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 <[email protected]>
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 &hellip; 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 &mdash; 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 
&mdash; 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) &mdash; 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 &mdash; 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 &mdash; basic 
auth <code>default:&lt;private-key&gt;</code> for admin APIs (scopes, rules, 
schemas, &hellip;)</li>
+                            <li><strong>PUBLIC</strong> 
<code>plainTextKey</code> &mdash; 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> &mdash; basic auth 
<code>default:&lt;private-key&gt;</code> for admin APIs (scopes, rules, 
schemas, &hellip;)</li>
                         </ul>
                     </div>
                 </div>
@@ -254,6 +260,15 @@ volumes:
                         <h3 class="h6 fw-semibold mb-2">Verify cluster &amp; 
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 &middot; 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> &mdash; 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> &mdash; basic auth 
<code>default:YOUR_PRIVATE_API_KEY</code> for scopes, rules, schemas, 
searches</li>
@@ -200,7 +206,7 @@ karaf@root()&gt; 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> &mdash; 
<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> &mdash; <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> &middot; <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()&gt; 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()&gt; unomi:start</code></pre>
     "source": {
       "itemId": "homepage",
       "itemType": "page",
-      "scope": "default"
+      "scope": "my-website"
     },
     "requiredProfileProperties": ["*"],
     "requiredSessionProperties": ["*"],
@@ -238,21 +260,6 @@ karaf@root()&gt; 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()&gt; 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()&gt; 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()&gt; log:set DEBUG 
org.apache.unomi.schema.impl.SchemaServiceImpl
 
 # Watch logs in real-time
 karaf@root()&gt; 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 
&lt;ID&gt;</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 
&lt;ID&gt;</code></p>
                 </div>
             </div>
         </div>

Reply via email to