You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@unomi.apache.org by sh...@apache.org on 2026/07/17 12:13:29 UTC
(unomi-site) 04/05: Fix Unomi 3.1 site examples against live API behavior (UNOMI-932)
This is an automated email from the ASF dual-hosted git repository.
sergehuber pushed a commit to branch UNOMI-932-unomi3-major-release-story
in repository https://gitbox.apache.org/repos/asf/unomi-site.git
commit 45d33ccef93f5c1c48e66a7531a8209f53a07673
Author: Serge Huber <sh...@apache.org>
AuthorDate: Thu Jul 16 12:10:59 2026 +0200
Fix Unomi 3.1 site examples against live API behavior (UNOMI-932)
Document API key regen for plainTextKey, require scopes before context/events, and put view pageInfo under target.properties so built-in referrer rules stay quiet.
---
src/main/webapp/blog/unomi-3-request-tracing.html | 26 ++++++++++-
src/main/webapp/blog/unomi-3-shell-commands.html | 19 ++++++--
src/main/webapp/get-started.html | 25 ++++++++--
src/main/webapp/llms-full.txt | 11 ++++-
src/main/webapp/llms.txt | 3 +-
src/main/webapp/tutorial.html | 56 +++++++++++++----------
6 files changed, 103 insertions(+), 37 deletions(-)
diff --git a/src/main/webapp/blog/unomi-3-request-tracing.html b/src/main/webapp/blog/unomi-3-request-tracing.html
index f5bfdf8..faf1858 100644
--- a/src/main/webapp/blog/unomi-3-request-tracing.html
+++ b/src/main/webapp/blog/unomi-3-request-tracing.html
@@ -51,7 +51,21 @@ layout: default
<pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>{"errorMessage":"Insufficient privileges to access tracing information"}</code></pre>
<p>Integration coverage: <code>EventsCollectorIT#testEventsCollectorWithExplain</code> and <code>#testEventsCollectorWithExplainUnauthorized</code>.</p>
+ <h2 class="h4 mt-5 mb-3">Prerequisite: create the scope</h2>
+ <p>JSON Schema validation rejects events whose <code>scope</code> is unknown for the tenant
+ (<code>Unknown scope value … for value myscope</code>). Create the scope first:</p>
+ <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST http://localhost:8181/cxs/scopes \
+ -u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \
+ -H 'Content-Type: application/json' \
+ -d '{
+ "itemId": "myscope",
+ "itemType": "scope",
+ "metadata": { "id": "myscope", "name": "My scope" }
+ }'</code></pre>
+
<h2 class="h4 mt-5 mb-3">Example: event collector</h2>
+ <p class="small text-muted">Include <code>target.properties.pageInfo</code> (at least <code>referringURL</code> / <code>pagePath</code>).
+ Built-in rules such as <code>sessionReferrer</code> read those fields; omitting them still works but logs noisy WARNs.</p>
<pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>curl -X POST 'http://localhost:8181/cxs/eventcollector?explain=true' \
-u 'YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY' \
-H 'Content-Type: application/json' \
@@ -70,7 +84,17 @@ layout: default
"target": {
"itemType": "page",
"scope": "myscope",
- "itemId": "homepage"
+ "itemId": "homepage",
+ "properties": {
+ "pageInfo": {
+ "pageID": "homepage",
+ "pagePath": "/home",
+ "pageName": "Home",
+ "destinationURL": "https://example.com/home",
+ "referringURL": "https://example.com/",
+ "language": "en"
+ }
+ }
}
}
]
diff --git a/src/main/webapp/blog/unomi-3-shell-commands.html b/src/main/webapp/blog/unomi-3-shell-commands.html
index 148e6ed..83497da 100644
--- a/src/main/webapp/blog/unomi-3-shell-commands.html
+++ b/src/main/webapp/blog/unomi-3-shell-commands.html
@@ -56,8 +56,11 @@ unomi:version
unomi:cache --stats</code></pre>
<h2 class="h4 mt-5 mb-3">Tenant context first</h2>
- <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Create a tenant (generates public + private API keys)
+ <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Create a tenant (hashed keys are stored; plaintext only via create apikey)
unomi:crud create tenant '{"itemId":"acme","name":"Acme Corp"}'
+unomi:crud create apikey '{"tenantId":"acme","keyType":"PUBLIC"}'
+unomi:crud create apikey '{"tenantId":"acme","keyType":"PRIVATE"}'
+# plaintext key is printed once for each create apikey — store it immediately
unomi:crud list tenant
unomi:tenant-set acme
@@ -115,6 +118,13 @@ unomi:crud update profile demo-ada '{"firstName":"Ada","lastName":"Lovelace","em
unomi:crud delete profile demo-ada</code></pre>
<p class="small text-muted">There are no separate <code>profile-list</code> / <code>profile-view</code> commands in Unomi 3 — use <code>unomi:crud list|read|delete profile</code>.</p>
+ <h3 class="h6 mt-4 mb-2">Scope first</h3>
+ <p class="small text-muted">Sessions and events must reference a scope that already exists for the tenant.
+ Without it, event schema validation logs <code>Unknown scope value</code> and rejects the payload.</p>
+ <pre class="code-block-light p-3 rounded-3 small overflow-auto"><code># Scope name lives under metadata (not a top-level "name" field)
+unomi:crud create scope '{"itemId":"myscope","metadata":{"id":"myscope","name":"My scope"}}'
+unomi:crud list scope</code></pre>
+
<h3 class="h6 mt-4 mb-2">Sessions</h3>
<pre class="code-block-light p-3 rounded-3 small overflow-auto"><code>unomi:crud help session
unomi:crud list session -n 20
@@ -134,11 +144,10 @@ unomi:crud delete session demo-session-1</code></pre>
unomi:crud list event -n 50
unomi:crud read event EVENT_ID
-# Send / persist a view event (create goes through EventService.send)
-unomi:crud create event '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"homepage"},"properties":{"pageInfo":{"pagePath":"/home"}},"persistent":true}'
+# View events: put pageInfo under target.properties (built-in sessionReferrer reads referringURL there)
+unomi:crud create event '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"homepage","properties":{"pageInfo":{"pagePath":"/home","pageName":"Home","destinationURL":"https://example.com/home","referringURL":"https://example.com/","language":"en"}}},"persistent":true}'
-# Re-send / update the same event id with new properties
-unomi:crud update event demo-event-1 '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"checkout"},"properties":{"pageInfo":{"pagePath":"/checkout"}},"persistent":true}'
+unomi:crud update event demo-event-1 '{"itemId":"demo-event-1","eventType":"view","scope":"myscope","profileId":"demo-ada","sessionId":"demo-session-1","source":{"itemType":"site","scope":"myscope","itemId":"mysite"},"target":{"itemType":"page","scope":"myscope","itemId":"checkout","properties":{"pageInfo":{"pagePath":"/checkout","pageName":"Checkout","destinationURL":"https://example.com/checkout","referringURL":"https://example.com/home","language":"en"}}},"persistent":true}'
unomi:crud delete event demo-event-1
diff --git a/src/main/webapp/get-started.html b/src/main/webapp/get-started.html
index 4c2ef2b..ede669f 100644
--- a/src/main/webapp/get-started.html
+++ b/src/main/webapp/get-started.html
@@ -32,7 +32,7 @@ structured_data: >
"@type": "HowToStep",
"position": 3,
"name": "Create a tenant",
- "text": "POST /cxs/tenants as karaf/karaf and save the public and private API keys from the response."
+ "text": "POST /cxs/tenants as karaf/karaf, then POST /cxs/tenants/{id}/apikeys?type=PUBLIC and type=PRIVATE and save each plainTextKey."
},
{
"@type": "HowToStep",
@@ -229,7 +229,7 @@ volumes:
<div class="step-number me-3 flex-shrink-0">3</div>
<div class="flex-grow-1">
<h3 class="h6 fw-semibold mb-2">Create a tenant (required in 3.1)</h3>
- <p class="small text-muted">System admin credentials create tenants. Save both API keys from the JSON response.</p>
+ <p class="small text-muted">System admin credentials create tenants. Creation stores hashed keys only (<code>maskedKey</code> in the response) — regenerate each key once and save <code>plainTextKey</code> immediately.</p>
<pre><code>curl -X POST http://localhost:8181/cxs/tenants \
--user karaf:karaf \
-H "Content-Type: application/json" \
@@ -239,10 +239,16 @@ volumes:
"name": "Default Tenant",
"description": "Default tenant for quick start"
}
- }'</code></pre>
+ }'
+
+# One-time plaintext secrets (replaces any existing key of that type)
+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</code></pre>
<ul class="small text-muted mt-2 mb-0">
- <li><strong>PUBLIC</strong> key — use as <code>X-Unomi-Api-Key</code> on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li>
- <li><strong>PRIVATE</strong> key — basic auth <code>default:<private-key></code> for admin APIs (scopes, rules, schemas, …)</li>
+ <li><strong>PUBLIC</strong> <code>plainTextKey</code> — use as <code>X-Unomi-Api-Key</code> on <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li>
+ <li><strong>PRIVATE</strong> <code>plainTextKey</code> — basic auth <code>default:<private-key></code> for admin APIs (scopes, rules, schemas, …)</li>
</ul>
</div>
</div>
@@ -254,6 +260,15 @@ volumes:
<h3 class="h6 fw-semibold mb-2">Verify cluster & request context</h3>
<p class="small text-muted mb-2">Cluster (system admin):</p>
<pre><code>curl -k https://localhost:9443/cxs/cluster -u karaf:karaf</code></pre>
+ <p class="small text-muted mt-3 mb-2">Create a scope before context or events (schema validation rejects unknown scopes):</p>
+ <pre><code>curl -X POST http://localhost:8181/cxs/scopes \
+ -u "default:YOUR_PRIVATE_API_KEY" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "itemId": "default",
+ "itemType": "scope",
+ "metadata": { "id": "default", "name": "Default scope" }
+ }'</code></pre>
<p class="small text-muted mt-3 mb-2">First context request (public API key required):</p>
<pre><code>curl -X POST "http://localhost:8181/cxs/context.json?sessionId=1234" \
-H "Content-Type: application/json" \
diff --git a/src/main/webapp/llms-full.txt b/src/main/webapp/llms-full.txt
index eae4cc2..6b1ae4d 100644
--- a/src/main/webapp/llms-full.txt
+++ b/src/main/webapp/llms-full.txt
@@ -193,7 +193,7 @@ services:
docker compose up
```
-Create a tenant, then call context with the public API key:
+Create a tenant, regenerate API keys for plaintext secrets, create a scope, then call context with the public API key:
```
curl -X POST http://localhost:8181/cxs/tenants \
@@ -201,6 +201,15 @@ curl -X POST http://localhost:8181/cxs/tenants \
-H "Content-Type: application/json" \
-d '{"requestedId":"default","properties":{"name":"Default Tenant"}}'
+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
+# Save plainTextKey from each response (tenant create only returns maskedKey)
+
+curl -X POST http://localhost:8181/cxs/scopes \
+ -u 'default:YOUR_PRIVATE_API_KEY' \
+ -H "Content-Type: application/json" \
+ -d '{"itemId":"default","itemType":"scope","metadata":{"id":"default","name":"Default scope"}}'
+
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" \
diff --git a/src/main/webapp/llms.txt b/src/main/webapp/llms.txt
index 3979623..8525ea2 100644
--- a/src/main/webapp/llms.txt
+++ b/src/main/webapp/llms.txt
@@ -69,10 +69,11 @@ The fastest way to try Apache Unomi 3.1 is with Docker Compose (Elasticsearch 9.
docker compose up
```
-Then create a tenant (`POST /cxs/tenants` as `karaf:karaf`) and save the returned API keys:
+Then create a tenant (`POST /cxs/tenants` as `karaf:karaf`), regenerate keys via `POST /cxs/tenants/{id}/apikeys?type=PUBLIC|PRIVATE` (save each `plainTextKey` — create only returns `maskedKey`), and create a scope before context/events:
- **Public key** → header `X-Unomi-Api-Key` for `/cxs/context.json` and `/cxs/eventcollector`
- **Private key** → basic auth `tenantId:privateApiKey` for admin APIs (scopes, rules, schemas, searches)
+- **Scope** → `POST /cxs/scopes` before any context or event that references that scope id
System admin (`karaf:karaf`) is for tenant management and cluster operations, not for public context/event calls in native 3.1 mode.
diff --git a/src/main/webapp/tutorial.html b/src/main/webapp/tutorial.html
index 5219309..ce34cd3 100644
--- a/src/main/webapp/tutorial.html
+++ b/src/main/webapp/tutorial.html
@@ -22,7 +22,7 @@ structured_data: >
"@type": "HowToStep",
"position": 2,
"name": "Create a tenant",
- "text": "POST /cxs/tenants as karaf/karaf and save the public and private API keys."
+ "text": "POST /cxs/tenants as karaf/karaf, then regenerate PUBLIC and PRIVATE keys via /cxs/tenants/{id}/apikeys and save each plainTextKey."
},
{
"@type": "HowToStep",
@@ -145,7 +145,7 @@ services:
<div class="col-lg-10">
<span class="badge badge-primary mb-3">Step 2 · Unomi 3.1</span>
<h2>Create a Tenant</h2>
- <p class="text-muted">In Unomi 3.1, data lives under a <strong>tenant</strong>. Create one with the system admin user, then use the returned API keys for everything else.</p>
+ <p class="text-muted">In Unomi 3.1, data lives under a <strong>tenant</strong>. Create one with the system admin user, then regenerate API keys to obtain the one-time plaintext values.</p>
<pre><code>curl -X POST http://localhost:8181/cxs/tenants \
--user karaf:karaf \
-H "Content-Type: application/json" \
@@ -155,8 +155,14 @@ services:
"name": "Default Tenant",
"description": "Tutorial tenant"
}
- }'</code></pre>
- <p class="small text-muted mt-3">From the response, copy:</p>
+ }'
+
+# 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</code></pre>
+ <p class="small text-muted mt-3">From each apikeys response, copy <code>plainTextKey</code>:</p>
<ul class="small">
<li><code>YOUR_PUBLIC_API_KEY</code> — header <code>X-Unomi-Api-Key</code> for <code>/cxs/context.json</code> and <code>/cxs/eventcollector</code></li>
<li><code>YOUR_PRIVATE_API_KEY</code> — basic auth <code>default:YOUR_PRIVATE_API_KEY</code> for scopes, rules, schemas, searches</li>
@@ -200,7 +206,7 @@ karaf@root()> unomi:start</code></pre>
<p>Create a tenant as in <a href="#create-tenant">Step 2</a>, then call context with <code>X-Unomi-Api-Key</code>. Cluster check: <code>https://localhost:9443/cxs/cluster</code> (<code>karaf</code>/<code>karaf</code>).</p>
<div class="alert alert-info small" role="alert">
- <i class="bi bi-terminal me-1"></i> <strong>Tip:</strong> SSH console <code>ssh -p 8102 karaf@localhost</code> — <code>profile-list</code>, <code>event-tail</code>, <code>rule-list</code>. Full detail: <a href="https://unomi.apache.org/manual/latest/index.html#_quick_start">manual quick start</a>.
+ <i class="bi bi-terminal me-1"></i> <strong>Tip:</strong> SSH console <code>ssh -p 8102 karaf@localhost</code> — <code>unomi:crud list profile</code>, <code>unomi:event-tail</code>, <code>unomi:crud list rule</code>. Full detail: <a href="/blog/unomi-3-shell-commands.html">shell commands post</a> · <a href="https://unomi.apache.org/manual/latest/index.html#_quick_start">manual quick start</a>.
</div>
</div>
</div>
@@ -220,6 +226,22 @@ karaf@root()> unomi:start</code></pre>
<h2>Your First API Calls</h2>
<p class="text-muted">With Unomi 3.1 running and a tenant created, explore the REST API. Replace <code>YOUR_PUBLIC_API_KEY</code> / <code>YOUR_PRIVATE_API_KEY</code> with values from <a href="#create-tenant">Step 2</a>.</p>
+ <!-- Create a scope first -->
+ <h3 class="h6 fw-semibold mt-4 mb-3">Create a scope</h3>
+ <p>Context requests and events must use a <strong>scope</strong> that already exists for the tenant.
+ Schema validation rejects unknown scopes (<code>Unknown scope value</code> in the logs):</p>
+<pre><code>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"
+ }
+ }'</code></pre>
+
<!-- Read context -->
<h3 class="h6 fw-semibold mt-4 mb-3">Read the current context</h3>
<p>The <code>/cxs/context.json</code> endpoint is the primary public endpoint. In 3.1 it requires a <strong>public API key</strong>:</p>
@@ -230,7 +252,7 @@ karaf@root()> unomi:start</code></pre>
"source": {
"itemId": "homepage",
"itemType": "page",
- "scope": "default"
+ "scope": "my-website"
},
"requiredProfileProperties": ["*"],
"requiredSessionProperties": ["*"],
@@ -238,21 +260,6 @@ karaf@root()> unomi:start</code></pre>
"requireScores": true
}'</code></pre>
- <!-- Create a scope -->
- <h3 class="h6 fw-semibold mt-4 mb-3">Create a scope</h3>
- <p>Events in Unomi must be associated with a <strong>scope</strong>. Create one before sending events:</p>
-<pre><code>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"
- }
- }'</code></pre>
-
<!-- Send a custom event -->
<h3 class="h6 fw-semibold mt-4 mb-3">Send a custom event</h3>
<p>Before sending a custom event, register a <strong>JSON Schema</strong> to validate it (required since Unomi 2.0):</p>
@@ -341,7 +348,7 @@ karaf@root()> unomi:start</code></pre>
-u "default:YOUR_PRIVATE_API_KEY"</code></pre>
<div class="alert alert-info small" role="alert">
- <i class="bi bi-terminal me-1"></i> <strong>SSH Console shortcuts:</strong> <code>event-tail</code> watches events in real time, <code>event-list</code> shows recent events, <code>profile-list</code> lists recently modified profiles, <code>profile-view PROFILE_UUID</code> shows profile details.
+ <i class="bi bi-terminal me-1"></i> <strong>SSH Console shortcuts:</strong> <code>unomi:event-tail</code> watches events in real time, <code>unomi:crud list event</code> / <code>unomi:crud list profile</code> list recent items, <code>unomi:crud read profile PROFILE_UUID</code> shows profile details.
</div>
</div>
</div>
@@ -383,6 +390,7 @@ karaf@root()> unomi:start</code></pre>
"pageName": document.title,
"pagePath": document.location.pathname,
"destinationURL": document.location.origin + document.location.pathname,
+ "referringURL": document.referrer || document.location.origin + "/",
"language": "en",
"categories": [],
"tags": []
@@ -489,7 +497,7 @@ const unomiWebTracker = useTracker();
}
}]
}'</code></pre>
- <p class="small text-muted">After creating this rule, reload your tracked page a few times, then check the profile with <code>profile-view PROFILE_UUID</code> in the SSH console to see the counter increase.</p>
+ <p class="small text-muted">After creating this rule, reload your tracked page a few times, then check the profile with <code>unomi:crud read profile PROFILE_UUID</code> in the SSH console to see the counter increase.</p>
<!-- Copy event properties to profile -->
<h3 class="h6 fw-semibold mt-4 mb-3">Map event data to a profile</h3>
@@ -667,7 +675,7 @@ karaf@root()> log:set DEBUG org.apache.unomi.schema.impl.SchemaServiceImpl
# Watch logs in real-time
karaf@root()> log:tail</code></pre>
- <p class="small text-muted mb-0">Useful commands: <code>event-tail</code>, <code>event-list</code>, <code>rule-list</code>, <code>rule-tail</code>, <code>profile-list</code>, <code>profile-view <ID></code></p>
+ <p class="small text-muted mb-0">Useful commands: <code>unomi:event-tail</code>, <code>unomi:rule-tail</code>, <code>unomi:crud list event|rule|profile</code>, <code>unomi:crud read profile <ID></code></p>
</div>
</div>
</div>