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