Skip to main content
Step 1

Quick Start with Docker

The fastest way to get Unomi 3.1 running. You only need Docker. Full compose options (including OpenSearch) are on the Getting Started page.

Create docker-compose.yml (Elasticsearch)

version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.4.3
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
    ports:
      - 9200:9200

  unomi:
    image: apache/unomi:3.1.0
    environment:
      - UNOMI_ELASTICSEARCH_ADDRESSES=elasticsearch:9200
      - UNOMI_THIRDPARTY_PROVIDER1_IPADDRESSES=0.0.0.0/0,::1,127.0.0.1
    ports:
      - 8181:8181
      - 9443:9443
      - 8102:8102
    depends_on:
      - elasticsearch

Start the environment

docker compose up

Verify the cluster

curl -k https://localhost:9443/cxs/cluster -u karaf:karaf

Default system credentials: karaf / karaf. Accept the self-signed certificate warning in development.



Step 2 · Unomi 3.1

Create a Tenant

In Unomi 3.1, data lives under a tenant. Create one with the system admin user, then regenerate API keys to obtain the one-time plaintext values.

curl -X POST http://localhost:8181/cxs/tenants \
  --user karaf:karaf \
  -H "Content-Type: application/json" \
  -d '{
    "requestedId": "default",
    "properties": {
      "name": "Default Tenant",
      "description": "Tutorial tenant"
    }
  }'

# Tenant create only returns maskedKey. Regenerate to get plainTextKey (shown once):
curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PUBLIC' \
  --user karaf:karaf
curl -X POST 'http://localhost:8181/cxs/tenants/default/apikeys?type=PRIVATE' \
  --user karaf:karaf

From each apikeys response, copy plainTextKey:

  • YOUR_PUBLIC_API_KEY — header X-Unomi-Api-Key for /cxs/context.json and /cxs/eventcollector
  • YOUR_PRIVATE_API_KEY — basic auth default:YOUR_PRIVATE_API_KEY for scopes, rules, schemas, searches

More: Getting Started auth table · Multi-tenancy post

Alternative to Docker

Manual Installation

Prefer running Unomi 3.1 on your machine? Match the versions used in the project quick start.

Prerequisites

  • Java 17 or later — set JAVA_HOME. OpenJDK works fine.
  • Elasticsearch 9.4.3 or OpenSearch 3.x

1. Start the search engine

Elasticsearch: set cluster.name: contextElasticSearch in config/elasticsearch.yml, then bin/elasticsearch.

OpenSearch: set cluster.name: opensearch-cluster and discovery.type: single-node, then bin/opensearch. In Karaf use unomi:setup -d=unomi-distribution-opensearch -f=true before unomi:start.

2. Download & start Apache Unomi

Download the Unomi 3.1.0 binary from the download page, extract it, then:

./bin/karaf
karaf@root()> unomi:start

3. Tenant + context

Create a tenant as in Step 2, then call context with X-Unomi-Api-Key. Cluster check: https://localhost:9443/cxs/cluster (karaf/karaf).


Step 3

Your First API Calls

With Unomi 3.1 running and a tenant created, explore the REST API. Replace YOUR_PUBLIC_API_KEY / YOUR_PRIVATE_API_KEY with values from Step 2.

Create a scope

Context requests and events must use a scope that already exists for the tenant. Schema validation rejects unknown scopes (Unknown scope value in the logs):

curl -X POST http://localhost:8181/cxs/scopes \
  -u "default:YOUR_PRIVATE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "itemId": "my-website",
    "itemType": "scope",
    "metadata": {
      "id": "my-website",
      "name": "My Website Scope"
    }
  }'

Read the current context

The /cxs/context.json endpoint is the primary public endpoint. In 3.1 it requires a public API key:

curl -X POST "http://localhost:8181/cxs/context.json?sessionId=1234" \
  -H "Content-Type: application/json" \
  -H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
  --data-raw '{
    "source": {
      "itemId": "homepage",
      "itemType": "page",
      "scope": "my-website"
    },
    "requiredProfileProperties": ["*"],
    "requiredSessionProperties": ["*"],
    "requireSegments": true,
    "requireScores": true
  }'

Send a custom event

Before sending a custom event, register a JSON Schema to validate it (required since Unomi 2.0):

curl -X POST http://localhost:8181/cxs/jsonSchema \
  -u "default:YOUR_PRIVATE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "$id": "https://unomi.apache.org/schemas/json/events/contactInfoSubmitted/1-0-0",
    "$schema": "https://json-schema.org/draft/2019-09/schema",
    "self": {
      "vendor": "org.apache.unomi",
      "name": "contactInfoSubmitted",
      "format": "jsonschema",
      "target": "events",
      "version": "1-0-0"
    },
    "title": "contactInfoSubmittedEvent",
    "type": "object",
    "allOf": [{ "$ref": "https://unomi.apache.org/schemas/json/event/1-0-0" }],
    "properties": {
      "source": { "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" },
      "target": { "$ref": "https://unomi.apache.org/schemas/json/item/1-0-0" },
      "properties": {
        "type": "object",
        "properties": {
          "firstName": { "type": ["null", "string"] },
          "lastName":  { "type": ["null", "string"] },
          "email":     { "type": ["null", "string"] }
        }
      }
    },
    "unevaluatedProperties": false
  }'

Now send the event via the event collector (the public endpoint for submitting events):

curl -X POST http://localhost:8181/cxs/eventcollector \
  -H "Content-Type: application/json" \
  -H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
  --data-raw '{
    "sessionId": "1234",
    "events": [{
      "eventType": "contactInfoSubmitted",
      "scope": "my-website",
      "source": {
        "itemType": "site",
        "scope": "my-website",
        "itemId": "mysite"
      },
      "target": {
        "itemType": "form",
        "scope": "my-website",
        "itemId": "contactForm"
      },
      "properties": {
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane.doe@example.com"
      }
    }]
  }'

Search events

Retrieve events matching a condition:

curl -X POST http://localhost:8181/cxs/events/search \
  -u "default:YOUR_PRIVATE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "offset": 0,
    "limit": 20,
    "sortby": "timeStamp:desc",
    "condition": {
      "type": "eventPropertyCondition",
      "parameterValues": {
        "propertyName": "properties.firstName",
        "comparisonOperator": "equals",
        "propertyValue": "Jane"
      }
    }
  }'

View a profile

Use the profile UUID (from the context-profile-id cookie or event response) to look up profile details:

curl http://localhost:8181/cxs/profiles/PROFILE_UUID \
  -u "default:YOUR_PRIVATE_API_KEY"

Step 4

Web Tracking Tutorial

Collect page views and enable personalization. Unomi 3.1 public endpoints require your tenant public API key on every context / event request.

Add the tracker to your page

Include the tracker script and initialize it (scope must exist under your tenant):

<!-- Load the Unomi web tracker -->
<script src="/tracker/unomi-web-tracker.min.js"></script>

<script>
(function () {
  var conf = {
    "scope": "my-website",
    "site": {
      "siteInfo": { "siteID": "my-website" }
    },
    "page": {
      "pageInfo": {
        "pageID": "home",
        "pageName": document.title,
        "pagePath": document.location.pathname,
        "destinationURL": document.location.origin + document.location.pathname,
        "referringURL": document.referrer || document.location.origin + "/",
        "language": "en",
        "categories": [],
        "tags": []
      },
      "attributes": {},
      "consentTypes": []
    },
    "events:": [],
    "wemInitConfig": {
      "contextServerUrl": document.location.origin,
      "timeoutInMilliseconds": "1500",
      "contextServerCookieName": "context-profile-id",
      "activateWem": true,
      "trackerSessionIdCookieName": "my-website-session-id",
      "trackerProfileIdCookieName": "my-website-profile-id"
      // If supported by your tracker build, also set apiKey: "YOUR_PUBLIC_API_KEY"
    }
  };

  // Unomi 3.1: ensure public endpoints receive the tenant public API key
  var originalOpen = XMLHttpRequest.prototype.open;
  var originalSend = XMLHttpRequest.prototype.send;
  XMLHttpRequest.prototype.open = function() {
    this._unomiUrl = arguments[1];
    return originalOpen.apply(this, arguments);
  };
  XMLHttpRequest.prototype.send = function() {
    if (this._unomiUrl && String(this._unomiUrl).indexOf("/cxs/") !== -1) {
      this.setRequestHeader("X-Unomi-Api-Key", "YOUR_PUBLIC_API_KEY");
    }
    return originalSend.apply(this, arguments);
  };

  // Generate a new session if needed
  if (unomiWebTracker.getCookie(conf.wemInitConfig.trackerSessionIdCookieName) == null) {
    unomiWebTracker.setCookie(conf.wemInitConfig.trackerSessionIdCookieName, unomiWebTracker.generateGuid(), 1);
  }

  unomiWebTracker.initTracker(conf);

  unomiWebTracker._registerCallback(function() {
    console.log("Unomi context loaded:", unomiWebTracker.getLoadedContext());
  }, 'My callback');

  unomiWebTracker.startTracker();
})();
</script>

Using the tracker as an NPM package

For JavaScript/TypeScript projects, install the tracker from npm:

npm install apache-unomi-tracker
# or
yarn add apache-unomi-tracker

Then import and use it:

import { useTracker } from "apache-unomi-tracker";

const unomiWebTracker = useTracker();
// ... configure and start as shown above

Step 5

Rules & Personalization

Rules let Unomi react to events in real time — updating profiles, triggering actions, or integrating with external systems.

Create a rule that counts page views

This rule increments a pageViewCount property on the visitor’s profile every time a view event is received:

curl -X POST http://localhost:8181/cxs/rules \
  -u "default:YOUR_PRIVATE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "metadata": {
      "id": "viewEventRule",
      "name": "View event rule",
      "description": "Increments pageViewCount on each page view"
    },
    "condition": {
      "type": "eventTypeCondition",
      "parameterValues": {
        "eventTypeId": "view"
      }
    },
    "actions": [{
      "type": "incrementPropertyAction",
      "parameterValues": {
        "propertyName": "pageViewCount"
      }
    }]
  }'

After creating this rule, reload your tracked page a few times, then check the profile with unomi:crud read profile PROFILE_UUID in the SSH console to see the counter increase.

Map event data to a profile

This rule listens for the contactInfoSubmitted event (from Step 3) and copies its properties to the profile:

curl -X POST http://localhost:8181/cxs/rules \
  -u "default:YOUR_PRIVATE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "metadata": {
      "id": "setContactInfo",
      "name": "Copy contact info to profile",
      "description": "Maps firstName, lastName, email from event to profile"
    },
    "condition": {
      "type": "eventTypeCondition",
      "parameterValues": {
        "eventTypeId": "contactInfoSubmitted"
      }
    },
    "actions": [
      {
        "type": "setPropertyAction",
        "parameterValues": {
          "setPropertyName": "properties(firstName)",
          "setPropertyValue": "eventProperty::properties(firstName)",
          "setPropertyStrategy": "alwaysSet"
        }
      },
      {
        "type": "setPropertyAction",
        "parameterValues": {
          "setPropertyName": "properties(lastName)",
          "setPropertyValue": "eventProperty::properties(lastName)",
          "setPropertyStrategy": "alwaysSet"
        }
      },
      {
        "type": "setPropertyAction",
        "parameterValues": {
          "setPropertyName": "properties(email)",
          "setPropertyValue": "eventProperty::properties(email)",
          "setPropertyStrategy": "alwaysSet"
        }
      }
    ]
  }'

Personalize content based on a profile property

Using the web tracker, you can register personalization rules that show different content based on profile data. For example, show a special message after 5 page views:

<div id="variant1" style="display:none">
  Welcome back! You've visited this page over 5 times.
</div>
<div id="variant2" style="display:none">
  Welcome! Keep exploring to unlock personalized content.
</div>

<script>
var variants = {
  "var1": { content: "variant1" },
  "var2": { content: "variant2" }
};

unomiWebTracker.registerPersonalizationObject({
  "id": "pageViewPersonalization",
  "strategy": "matching-first",
  "strategyOptions": { "fallback": "var2" },
  "contents": [{
    "id": "var1",
    "filters": [{
      "condition": {
        "type": "profilePropertyCondition",
        "parameterValues": {
          "propertyName": "properties.pageViewCount",
          "comparisonOperator": "greaterThan",
          "propertyValueInteger": 5
        }
      }
    }]
  }, {
    "id": "var2"
  }]
}, variants, false, function (successfulFilters, selectedFilter) {
  if (selectedFilter) {
    document.getElementById(selectedFilter.content).style.display = '';
  }
});
</script>

The personalization engine evaluates conditions server-side against the visitor’s profile and returns which variant to display, keeping your business logic private.


Next Steps

You now have a working Unomi 3.1 environment with a tenant, event tracking, profile management, rules, and personalization. Here’s where to go next.

Full Documentation

Dive deeper into segmentation, scoring, conditions, and actions in the comprehensive manual.

Read the manual

REST API Reference

Explore every endpoint for profiles, events, segments, rules, and more.

Browse the API

Getting Started

Docker + OpenSearch paths, auth cheat sheet, and production notes.

Open Getting Started

Multi-tenancy

How tenants and API keys work across the CDP.

Read the deep dive

Integrations

See how organizations use Unomi with CMS, CRM, e-commerce, and other platforms.

View integrations

Get Help

Stuck? The community is friendly and responsive on the mailing list and Slack.

Join the community

Debugging Tip

Enable debug logging via the Karaf SSH console to see detailed event validation and rule execution logs:

# Connect to Karaf SSH console
ssh -p 8102 karaf@localhost

# Enable schema validation debug logs
karaf@root()> log:set DEBUG org.apache.unomi.schema.impl.SchemaServiceImpl

# Watch logs in real-time
karaf@root()> log:tail

Useful commands: unomi:event-tail, unomi:rule-tail, unomi:crud list event|rule|profile, unomi:crud read profile <ID>