This is an automated email from the ASF dual-hosted git repository.

shuber pushed a commit to branch unomi-3-dev
in repository https://gitbox.apache.org/repos/asf/unomi.git


The following commit(s) were added to refs/heads/unomi-3-dev by this push:
     new 79ae744bc UNOMI-139 Update documentation for Apache Unomi 3.x
79ae744bc is described below

commit 79ae744bc30395402521f928f9a4f41b1a66a02f
Author: Serge Huber <[email protected]>
AuthorDate: Thu Jan 15 18:19:22 2026 +0100

    UNOMI-139 Update documentation for Apache Unomi 3.x
    
    - Updated pom.xml to reflect new versions for docker-maven-plugin and 
asciidoctor-maven-plugin.
    - Revised authentication examples in the documentation to use tenant 
credentials instead of default admin credentials.
    - Enhanced clarity on tenant resolution requirements for API requests, 
emphasizing the use of public and private API keys.
    - Added notes to various sections to guide users on replacing placeholders 
with actual tenant IDs and private keys.
    - Improved consistency in API request examples across multiple Asciidoc 
files.
---
 manual/src/main/asciidoc/configuration.adoc        |   32 +-
 manual/src/main/asciidoc/consent-api.adoc          |    2 +
 manual/src/main/asciidoc/datamodel.adoc            |    2 +-
 manual/src/main/asciidoc/getting-started.adoc      |   46 +-
 manual/src/main/asciidoc/graphql-examples.adoc     |    2 +-
 .../main/asciidoc/how-profile-tracking-works.adoc  | 1189 +++++++++++++++++++-
 .../main/asciidoc/jsonSchema/json-schema-api.adoc  |   16 +-
 .../asciidoc/jsonSchema/json-schema-develop.adoc   |    8 +-
 manual/src/main/asciidoc/privacy.adoc              |    5 +-
 manual/src/main/asciidoc/recipes.adoc              |   18 +-
 manual/src/main/asciidoc/request-examples.adoc     |   52 +-
 .../src/main/asciidoc/samples/twitter-sample.adoc  |    2 +
 manual/src/main/asciidoc/tutorial.adoc             |   10 +-
 manual/src/main/asciidoc/updating-events.adoc      |    3 +-
 pom.xml                                            |    2 +-
 15 files changed, 1310 insertions(+), 79 deletions(-)

diff --git a/manual/src/main/asciidoc/configuration.adoc 
b/manual/src/main/asciidoc/configuration.adoc
index b607854bb..ec457cc46 100644
--- a/manual/src/main/asciidoc/configuration.adoc
+++ b/manual/src/main/asciidoc/configuration.adoc
@@ -386,12 +386,12 @@ unomi:tenant-generate-key mytenant PUBLIC 30
 
 There are three ways to authenticate with the Unomi API:
 
-1. JAAS Authentication (Full Admin Access):
+1. Tenant Authentication (Recommended for most endpoints):
 [source,bash]
 ----
-# List all profiles (admin access)
+# List all profiles (tenant access)
 curl -X GET "http://localhost:8181/cxs/profiles"; \
-  -u karaf:karaf \
+  --user "TENANT_ID:PRIVATE_KEY" \
   -H "Accept: application/json"
 
 # Response (HTTP 200 OK):
@@ -443,7 +443,7 @@ curl -X POST "http://localhost:8181/cxs/context.json"; \
 ----
 # Get profiles using tenant credentials
 curl -X GET "http://localhost:8181/cxs/profiles"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Accept: application/json"
 
 # Response (HTTP 200 OK):
@@ -525,12 +525,12 @@ Example private endpoint access:
 ----
 # Get segment details
 curl -X GET "http://localhost:8181/cxs/segments/important-customers"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Accept: application/json"
 
 # Create a new segment
 curl -X POST "http://localhost:8181/cxs/segments"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Content-Type: application/json" \
   -d '{
     "itemId": "high-value-customers",
@@ -795,17 +795,19 @@ Deploy/update an Action:
 [source,bash]
 ----
 curl -X POST 'http://localhost:8181/cxs/groovyActions' \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 --form 'file=@"<file location>"'
 ----
 
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key.
+
 A Groovy Action can be updated by submitting another Action with the same id.
 
 Delete an Action:
 [source,bash]
 ----
 curl -X DELETE 'http://localhost:8181/cxs/groovyActions/<Action id>' \
---user karaf:karaf
+--user "TENANT_ID:PRIVATE_KEY"
 ----
 
 Note that when a groovy action is deleted by the API, the action type 
associated with this action will also be deleted.
@@ -834,7 +836,7 @@ Once the action has been created you need to submit it to 
Unomi (from the same f
 [source,bash]
 ----
 curl -X POST 'http://localhost:8181/cxs/groovyActions' \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 --form '[email protected]'
 ----
 
@@ -844,7 +846,7 @@ Finally, register a rule to trigger execution of the groovy 
action:
 [source,bash]
 ----
 curl -X POST 'http://localhost:8181/cxs/rules' \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 --header 'Content-Type: application/json' \
 --data-raw '{
  "metadata": {
@@ -877,14 +879,14 @@ Once you're done with the Hello World! action, it can be 
deleted using the follo
 [source,bash]
 ----
 curl -X DELETE 
'http://localhost:8181/cxs/groovyActions/helloWorldGroovyAction' \
---user karaf:karaf
+--user "TENANT_ID:PRIVATE_KEY"
 ----
 
 And the corresponding rule can be deleted using the following command:
 [source,bash]
 ----
 curl -X DELETE 'http://localhost:8181/cxs/rules/scriptGroovyActionRule' \
---user karaf:karaf
+--user "TENANT_ID:PRIVATE_KEY"
 ----
 
 ===== Inject an OSGI service in a groovy script
@@ -1882,7 +1884,7 @@ curl -X POST "http://localhost:8181/cxs/context.json"; \
 ----
 # Get profiles using tenant credentials
 curl -X GET "http://localhost:8181/cxs/profiles"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Accept: application/json"
 
 # Response (HTTP 200 OK):
@@ -1964,12 +1966,12 @@ Example private endpoint access:
 ----
 # Get segment details
 curl -X GET "http://localhost:8181/cxs/segments/important-customers"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Accept: application/json"
 
 # Create a new segment
 curl -X POST "http://localhost:8181/cxs/segments"; \
-  -H "Authorization: Basic $(echo -n 
'mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6' | base64)" \
+  --user "mytenant:1a2b3c4d-5e6f-7g8h-9i0j-k1l2m3n4o5p6" \
   -H "Content-Type: application/json" \
   -d '{
     "itemId": "high-value-customers",
diff --git a/manual/src/main/asciidoc/consent-api.adoc 
b/manual/src/main/asciidoc/consent-api.adoc
index 4086ed7ab..ad23f7ac5 100644
--- a/manual/src/main/asciidoc/consent-api.adoc
+++ b/manual/src/main/asciidoc/consent-api.adoc
@@ -36,6 +36,7 @@ you can simply retrieve a profile with the following request:
 ----
 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" \
 -d @- <<'EOF'
 {
     "source": {
@@ -127,6 +128,7 @@ You could send it using the following curl request:
 ----
 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" \
 -d @- <<'EOF'
 {
     "source":{
diff --git a/manual/src/main/asciidoc/datamodel.adoc 
b/manual/src/main/asciidoc/datamodel.adoc
index 88de04fad..2f4d17e0a 100755
--- a/manual/src/main/asciidoc/datamodel.adoc
+++ b/manual/src/main/asciidoc/datamodel.adoc
@@ -683,7 +683,7 @@ Here is an example of a simple segment definition 
registered using the REST API:
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/segments \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 -d @- <<'EOF'
 {
diff --git a/manual/src/main/asciidoc/getting-started.adoc 
b/manual/src/main/asciidoc/getting-started.adoc
index f6b6d09f1..c7acd9121 100644
--- a/manual/src/main/asciidoc/getting-started.adoc
+++ b/manual/src/main/asciidoc/getting-started.adoc
@@ -67,10 +67,52 @@ Initializing profile service endpoint...
 Initializing cluster service endpoint...
 ----
 
-This indicates that all the Unomi services are started and ready to react to 
requests. You can then:
+This indicates that all the Unomi services are started and ready to react to 
requests. 
+
+Before you can use the API, you need to create a tenant:
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/tenants \
+  --user karaf:karaf \
+  -H "Content-Type: application/json" \
+  -d '{
+    "requestedId": "default",
+    "properties": {
+      "name": "Default Tenant",
+      "description": "Default tenant for getting started"
+    }
+  }'
+----
+
+Save the API keys from the response - you'll need them for all subsequent API 
calls.
+
+Now you can:
 
 1. Access the RESTful services list: `http://localhost:8181/cxs`
-2. Retrieve an initial context: `http://localhost:8181/cxs/context.json`
+
+2. Retrieve an initial context:
+
+[source,bash]
+----
+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>" \
+  -d '{
+    "source": {
+      "itemId": "homepage",
+      "itemType": "page",
+      "scope": "default"
+    }
+  }'
+----
+
+[NOTE]
+====
+This request will automatically create a profile and session if they don't 
exist, and return a cookie with the profile ID. See 
<<_how_profile_tracking_works,How profile tracking works>> for details.
+====
+
++
 3. View the introduction page: `http://localhost:8181`
 
 Also now that your service is up and running you can go look at the
diff --git a/manual/src/main/asciidoc/graphql-examples.adoc 
b/manual/src/main/asciidoc/graphql-examples.adoc
index e5043052d..4d0537ac3 100644
--- a/manual/src/main/asciidoc/graphql-examples.adoc
+++ b/manual/src/main/asciidoc/graphql-examples.adoc
@@ -175,7 +175,7 @@ To make this query work you need to supply authorization 
token in the `HTTP head
 }
 ----
 
-The above header adds `Basic` authorization scheme with base64 encoded 
`karaf:karaf` value to the request.
+NOTE: When using curl, you can use the `--user` option instead of manually 
encoding credentials. For example, `--user karaf:karaf` automatically handles 
Base64 encoding for Basic authentication.
 
 The result will now show the list of profiles:
 
diff --git a/manual/src/main/asciidoc/how-profile-tracking-works.adoc 
b/manual/src/main/asciidoc/how-profile-tracking-works.adoc
index aa8597f5b..db3e9ebd4 100644
--- a/manual/src/main/asciidoc/how-profile-tracking-works.adoc
+++ b/manual/src/main/asciidoc/how-profile-tracking-works.adoc
@@ -14,22 +14,1173 @@
 [[_how_profile_tracking_works]]
 === How profile tracking works
 
-In this section you will learn how Apache Unomi keeps track of visitors.
-
-==== Steps
-
-1. A visitor comes to a website
-2. The web server resolves a previous request session ID if it exists, or if 
it doesn't it create a new sessionID
-3. A request to Apache Unomi's /cxs/context.json servlet is made passing the 
web server session ID as a query parameter
-4. Unomi uses the sessionID and tries to load an existing session, if none is 
found a new session is created with the
-ID passed by the web server
-5. If a session was found, the profile ID is extracted from the session and if 
it not found, Unomi looks for a cookie
-called `context-profile-id` to read the profileID. If no profileID is found or 
if the session didn't exist, a new
-profile ID is created by Apache Unomi
-6. If the profile ID existed, the corresponding profile is loaded by Apache 
Unomi, otherwise a new profile is created
-7. If events were passed along with the request to the context.json endpoint, 
they are processed against the profile
-8. The updated profile is sent back as a response to the context.json request. 
Along with the response
-
-It is important to note that the profileID is always server-generated. 
Injecting a custom cookie with a non-valid
-profile ID will result in failure to load the profile. Profile ID are UUIDs, 
which make them (pretty) safe from brute-
-forcing.
+In this section you will learn how Apache Unomi keeps track of visitors and 
processes context requests.
+
+==== Overview
+
+When a visitor interacts with a website that uses Apache Unomi, a single 
request to the `/cxs/context.json` or `/cxs/context.js` endpoint can perform 
multiple operations:
+
+* Automatically create or load a visitor profile
+* Automatically create or load a visitor session
+* Process events (if provided)
+* Execute rules triggered by those events
+* Resolve personalization (if requested)
+* Set a cookie with the profile ID for future requests
+
+All of this happens in a single request-response cycle, making Apache Unomi 
efficient and easy to integrate.
+
+[plantuml]
+----
+@startuml
+title Overview: Single Request Processing Flow
+
+Client -> ContextEndpoint: POST /cxs/context.json\n+ X-Unomi-Api-Key\n+ 
sessionId\n+ events (optional)\n+ filters (optional)\n+ personalizations 
(optional)
+activate ContextEndpoint
+
+ContextEndpoint -> AuthenticationFilter: Resolve tenant
+activate AuthenticationFilter
+AuthenticationFilter -> TenantService: Lookup tenant by API key
+TenantService --> AuthenticationFilter: Tenant ID
+AuthenticationFilter --> ContextEndpoint: Tenant context set
+deactivate AuthenticationFilter
+
+ContextEndpoint -> RestServiceUtils: initEventsRequest()
+activate RestServiceUtils
+RestServiceUtils -> RestServiceUtils: Resolve profile ID\n(from 
cookie/parameter)
+RestServiceUtils -> ProfileService: Load profile
+alt Profile not found
+    RestServiceUtils -> RestServiceUtils: Create new profile\n(generate UUID)
+end
+RestServiceUtils -> ProfileService: Load session
+alt Session not found
+    RestServiceUtils -> RestServiceUtils: Create new session
+    RestServiceUtils -> EventService: Send sessionCreated event
+end
+alt Profile was created
+    RestServiceUtils -> EventService: Send profileUpdated event
+end
+RestServiceUtils --> ContextEndpoint: EventsRequestContext
+deactivate RestServiceUtils
+
+alt Events provided
+    ContextEndpoint -> RestServiceUtils: performEventsRequest()
+    activate RestServiceUtils
+    loop For each event
+        RestServiceUtils -> EventService: Send event
+        EventService -> RulesService: Execute matching rules
+        RulesService -> RulesService: Update profile/segments/scores
+    end
+    RestServiceUtils --> ContextEndpoint: Updated context
+    deactivate RestServiceUtils
+end
+
+alt Filters provided
+    ContextEndpoint -> PersonalizationService: Filter content
+    PersonalizationService -> PersonalizationService: Evaluate 
conditions\n(using updated profile)
+    PersonalizationService --> ContextEndpoint: Filtering results
+end
+
+alt Personalizations provided
+    ContextEndpoint -> PersonalizationService: Resolve personalization
+    PersonalizationService -> PersonalizationService: Evaluate filters\n(using 
updated profile)
+    PersonalizationService --> ContextEndpoint: Selected content
+end
+
+ContextEndpoint -> RestServiceUtils: finalizeEventsRequest()
+activate RestServiceUtils
+RestServiceUtils -> ProfileService: Save profile (if updated)
+RestServiceUtils -> ProfileService: Save session (if updated)
+RestServiceUtils -> HttpUtils: Generate cookie string
+RestServiceUtils --> ContextEndpoint: Cookie set in response
+deactivate RestServiceUtils
+
+ContextEndpoint --> Client: ContextResponse\n+ Profile data\n+ Session data\n+ 
Personalization results\n+ Set-Cookie header
+deactivate ContextEndpoint
+
+@enduml
+----
+
+==== Detailed Request Flow
+
+When a request is made to `/cxs/context.json` or `/cxs/context.js`, Apache 
Unomi processes it through the following steps:
+
+[IMPORTANT]
+====
+**Tenant Resolution (Version 3.1+)**
+
+Starting with Apache Unomi 3.1, tenant resolution is mandatory for all 
requests to `/cxs/context.json` and `/cxs/eventcollector` endpoints. Apache 
Unomi must know which tenant's data to use before processing the request. The 
tenant is resolved through one of the following methods (in priority order):
+
+1. **Public API Key Authentication** (Recommended for public endpoints):
+   * Send the `X-Unomi-Api-Key` header with a public API key
+   * Apache Unomi automatically resolves the tenant from the API key
+   * This is the recommended method for public-facing applications
+
+2. **Basic Authentication with Private API Key**:
+   * Use Basic Auth with format: `tenantId:privateApiKey`
+   * The tenant is resolved from the credentials
+   * This is used for administrative or server-to-server requests
+
+3. **Tenant ID Header** (When using JAAS authentication):
+   * Send the `X-Unomi-Tenant-Id` header with the tenant ID
+   * Used when authenticating via JAAS (e.g., `karaf:karaf`)
+   * The tenant ID must exist in the system
+
+If no tenant can be resolved, the request will fail with an `UNAUTHORIZED` 
(401) error. This ensures that all data operations are scoped to the correct 
tenant in multi-tenant deployments.
+====
+
+===== Example: Tenant Resolution
+
+Here are examples of how to authenticate and resolve the tenant:
+
+**Example 1: Using Public API Key (Recommended)**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: public-key-abc123..." \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+The tenant is automatically resolved from the public API key. No additional 
headers needed.
+
+**Example 2: Using Basic Auth with Private API Key**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+--user "mytenant:private-key-xyz789..." \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+The tenant is resolved from the Basic Auth credentials (`mytenant` is the 
tenant ID).
+
+**Example 3: Using JAAS Auth with Tenant Header**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+--user "karaf:karaf" \
+-H "X-Unomi-Tenant-Id: mytenant" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+The tenant is resolved from the `X-Unomi-Tenant-Id` header when using JAAS 
authentication.
+
+[plantuml]
+----
+@startuml
+title Tenant Resolution Flow (Version 3.1+)
+
+Client -> AuthenticationFilter: Request with authentication
+activate AuthenticationFilter
+
+alt Public API Key (Recommended)
+    Client -> AuthenticationFilter: X-Unomi-Api-Key header
+    AuthenticationFilter -> TenantService: getTenantByApiKey(apiKey, PUBLIC)
+    TenantService --> AuthenticationFilter: Tenant
+    AuthenticationFilter -> ExecutionContextManager: Set tenant context
+    AuthenticationFilter --> Client: Request authorized
+else Basic Auth with Private Key
+    Client -> AuthenticationFilter: Basic Auth: tenantId:privateKey
+    AuthenticationFilter -> TenantService: validateApiKeyWithType(tenantId, 
key, PRIVATE)
+    TenantService --> AuthenticationFilter: Valid
+    AuthenticationFilter -> ExecutionContextManager: Set tenant context
+    AuthenticationFilter --> Client: Request authorized
+else JAAS Auth with Tenant Header
+    Client -> AuthenticationFilter: JAAS Auth + X-Unomi-Tenant-Id header
+    AuthenticationFilter -> TenantService: getTenant(tenantId)
+    TenantService --> AuthenticationFilter: Tenant
+    AuthenticationFilter -> ExecutionContextManager: Set tenant context
+    AuthenticationFilter --> Client: Request authorized
+else No Tenant Resolved
+    AuthenticationFilter --> Client: 401 UNAUTHORIZED
+end
+
+deactivate AuthenticationFilter
+
+note right of AuthenticationFilter
+    Tenant resolution is mandatory
+    in version 3.1+ for context.json
+    and eventcollector endpoints
+end note
+
+@enduml
+----
+
+===== Step 1: Profile Identification and Creation
+
+[IMPORTANT]
+====
+At least one of the following must be provided in the request: `sessionId`, 
`profileId` (as a parameter or in a cookie), or `personaId`. If none of these 
are provided, the request will fail with a `BadRequestException` (400 error).
+====
+
+Apache Unomi attempts to identify the visitor's profile through the following 
process:
+
+1. **Profile ID Resolution**: 
+   * First checks for a `profileId` parameter in the request
+   * If not found, looks for a cookie named `context-profile-id` (configurable 
via `org.apache.unomi.profile.cookie.name`)
+   * Cookie values are validated against a JSON schema - invalid values (e.g., 
containing script tags) will cause a `400 Bad Request` error
+   * The resolved profile ID is used to attempt loading the profile from the 
database
+
+2. **Session Profile Override** (if session exists):
+   * If a session is found (see Step 2) and contains a profile ID that differs 
from the cookie/profileId parameter
+   * Apache Unomi uses the session's profile ID instead (this handles cases 
where a user switches accounts)
+   * The profile is reloaded from the database using the session's profile ID
+
+3. **Profile Creation**: If no profile ID is found or the profile doesn't 
exist:
+   * If a profile ID was provided (from parameter or cookie) but doesn't exist 
in the database, creates a new profile with that ID
+   * If no profile ID was found, generates a new UUID as the profile ID and 
creates a new profile
+   * Sets the `firstVisit` property to the current timestamp
+   * Marks the profile for persistence
+   * Note: The `profileUpdated` event is sent after session creation (if a 
session is created) to ensure proper event ordering
+
+This ensures that profiles are always available for processing, even for 
first-time visitors or after database resets.
+
+[IMPORTANT]
+====
+The profile ID is always server-generated (UUID format). Even if a client 
sends a custom profile ID in a cookie or parameter, Apache Unomi validates it 
exists in the database. If it doesn't exist, a new profile is created 
(potentially with that ID if provided via parameter, or with a newly generated 
UUID). This makes profile IDs secure and prevents profile ID manipulation.
+====
+
+===== Example: Profile Identification
+
+**Example 1: First-Time Visitor (No Cookie)**
+
+[source,bash]
+----
+# Request (no cookie sent)
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+**Response Headers:**
+[source,http]
+----
+Set-Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890; Path=/; 
Max-Age=31536000; SameSite=Lax
+----
+
+**Response Body:**
+[source,json]
+----
+{
+    "profileId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+    "sessionId": "1234"
+}
+----
+
+Apache Unomi generated a new UUID (`a1b2c3d4-e5f6-7890-abcd-ef1234567890`) and 
created a new profile. The profile ID is returned in both the response body and 
the `Set-Cookie` header.
+
+**Example 2: Returning Visitor (With Cookie)**
+
+[source,bash]
+----
+# Request (browser automatically sends cookie)
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+**Response:**
+[source,json]
+----
+{
+    "profileId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+    "sessionId": "1234"
+}
+----
+
+Apache Unomi loaded the existing profile using the profile ID from the cookie.
+
+**Example 3: Using profileId Parameter**
+
+[source,bash]
+----
+# Request with explicit profileId parameter
+curl -X POST 
"http://localhost:8181/cxs/context.json?sessionId=1234&profileId=a1b2c3d4-e5f6-7890-abcd-ef1234567890";
 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+The `profileId` parameter takes precedence over the cookie value.
+
+[plantuml]
+----
+@startuml
+title Profile and Session Identification Flow
+
+RestServiceUtils -> RestServiceUtils: Get profileId from parameter
+alt profileId parameter exists
+    RestServiceUtils -> ProfileService: load(profileId)
+else Check cookie
+    RestServiceUtils -> HttpServletRequest: getCookie("context-profile-id")
+    HttpServletRequest --> RestServiceUtils: profileId from cookie
+    RestServiceUtils -> ProfileService: load(profileId)
+end
+
+alt Profile found
+    RestServiceUtils -> RestServiceUtils: Use existing profile
+else Profile not found
+    RestServiceUtils -> RestServiceUtils: createNewProfile(profileId or null)
+    RestServiceUtils -> RestServiceUtils: Generate UUID if needed
+    RestServiceUtils -> RestServiceUtils: Set firstVisit property
+    RestServiceUtils -> EventService: Send profileUpdated event
+    RestServiceUtils -> RestServiceUtils: Mark for persistence
+end
+
+alt sessionId provided
+    RestServiceUtils -> ProfileService: loadSession(sessionId)
+    alt Session found
+        RestServiceUtils -> RestServiceUtils: Check session profile
+        alt Session profile differs
+            RestServiceUtils -> ProfileService: load(sessionProfileId)
+            RestServiceUtils -> RestServiceUtils: Use session's profile
+        end
+    else Session not found
+        RestServiceUtils -> RestServiceUtils: Create new Session(sessionId, 
profile)
+        RestServiceUtils -> EventService: Send sessionCreated event
+        RestServiceUtils -> RestServiceUtils: Mark for persistence
+    end
+end
+
+@enduml
+----
+
+===== Step 2: Session Identification and Creation
+
+If a `sessionId` is provided in the request:
+
+1. **Session Loading**: Apache Unomi attempts to load the existing session 
from the database
+2. **Profile Association**: If a session is found, it may contain a profile ID 
that takes precedence over the cookie/profileId parameter
+3. **Session Creation**: If no session is found or the session is invalidated:
+   * Creates a new session with the provided `sessionId`
+   * Associates the session with the current profile (or anonymous profile if 
privacy settings require it)
+   * Sends a `sessionCreated` event (this happens before the `profileUpdated` 
event if both profile and session are created)
+   * Marks the session for persistence
+   * After session creation, if a new profile was created, sends a 
`profileUpdated` event (non-persistent) to mark the profile creation
+
+If no `sessionId` is provided, a transient (non-persisted) session may be used 
for the request.
+
+===== Example: Session Creation
+
+**Example: Creating a New Session**
+
+[source,bash]
+----
+# First request with a new sessionId
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=abc-123-xyz \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+Apache Unomi will:
+1. Check if session `abc-123-xyz` exists
+2. If not found, create a new session with ID `abc-123-xyz`
+3. Associate it with the current profile
+4. Send a `sessionCreated` event
+5. Save the session to the database
+
+**Subsequent Request with Same Session:**
+
+[source,bash]
+----
+# Request with existing sessionId
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=abc-123-xyz \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "product-page",
+        "itemType": "page",
+        "scope": "mydigital"
+    }
+}'
+----
+
+Apache Unomi loads the existing session `abc-123-xyz` and uses the profile 
associated with it.
+
+===== Step 3: Event Processing
+
+If events are provided in the request body:
+
+1. **Event Validation**: Each event is validated against its JSON schema
+2. **Event Authorization**: 
+   * Events are checked for tenant authorization (the tenant must be 
authorized to send the event type)
+   * Events are filtered based on privacy settings (if the profile has opted 
out of certain event types)
+   * Events are checked against IP address restrictions if configured
+3. **Event Execution**: For each valid event:
+   * The event is sent to the event service
+   * All matching rules are automatically executed (this happens synchronously 
during the request)
+   * Rules can trigger actions that modify the profile, session, or trigger 
other events
+   * Profile segments and scores are recalculated based on rule execution
+   * If the profile is updated during rule execution, the updated profile is 
used for subsequent events in the same request
+4. **Profile Updates**: If events cause profile changes, the profile is marked 
for persistence
+
+This is where the power of Apache Unomi's rule engine comes into play - events 
automatically trigger rules, which can perform complex logic, update profiles, 
add segments, calculate scores, and more. All of this happens within the same 
request, ensuring that personalization and subsequent operations use the most 
up-to-date profile state.
+
+===== Example: Event Processing with Rule Execution
+
+**Example: Sending a View Event**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "product-page",
+        "itemType": "page",
+        "scope": "mydigital"
+    },
+    "events": [
+        {
+            "eventType": "view",
+            "scope": "mydigital",
+            "source": {
+                "itemType": "site",
+                "scope": "mydigital",
+                "itemId": "mysite"
+            },
+            "target": {
+                "itemType": "product",
+                "scope": "mydigital",
+                "itemId": "product-123",
+                "properties": {
+                    "name": "Awesome Product",
+                    "category": "electronics",
+                    "price": 99.99
+                }
+            }
+        }
+    ]
+}'
+----
+
+**What Happens:**
+
+1. The `view` event is validated against its schema
+2. The event is checked for tenant authorization
+3. The event is sent to the event service
+4. All rules matching the `view` event are executed:
+   * Rules might increment a `pageViewCount` property
+   * Rules might add the profile to a "product-viewers" segment
+   * Rules might calculate a score based on product category
+   * Rules might trigger additional events
+5. The profile is updated with any changes from rule execution
+6. The updated profile is used for personalization (if requested)
+
+**Response:**
+[source,json]
+----
+{
+    "profileId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+    "sessionId": "1234",
+    "processedEvents": 1
+}
+----
+
+The `processedEvents` field indicates that 1 event was successfully processed. 
The profile may have been updated by rules triggered by this event.
+
+[plantuml]
+----
+@startuml
+title Event Processing and Rule Execution Flow
+
+RestServiceUtils -> RestServiceUtils: performEventsRequest(events)
+activate RestServiceUtils
+
+loop For each event in request
+    RestServiceUtils -> SchemaService: Validate event schema
+    alt Event valid
+        RestServiceUtils -> RestServiceUtils: Check tenant authorization
+        RestServiceUtils -> PrivacyService: Check privacy filters
+        alt Event authorized and not filtered
+            RestServiceUtils -> EventService: send(event)
+            activate EventService
+            EventService -> RulesService: Find matching rules
+            activate RulesService
+            loop For each matching rule
+                RulesService -> RulesService: Evaluate rule conditions
+                alt Conditions match
+                    RulesService -> RulesService: Execute rule actions
+                    RulesService -> ProfileService: Update profile properties
+                    RulesService -> ProfileService: Recalculate segments
+                    RulesService -> ProfileService: Recalculate scores
+                    RulesService -> EventService: Trigger additional events 
(if any)
+                end
+            end
+            deactivate RulesService
+            EventService --> RestServiceUtils: Event processed
+            deactivate EventService
+            RestServiceUtils -> RestServiceUtils: Update profile from event
+        else Event filtered or not authorized
+            RestServiceUtils -> RestServiceUtils: Skip event
+        end
+    else Event invalid
+        RestServiceUtils -> RestServiceUtils: Skip event
+    end
+end
+
+RestServiceUtils -> RestServiceUtils: Mark profile for persistence\nif changes 
occurred
+deactivate RestServiceUtils
+
+note right of RulesService
+    Rules execute synchronously
+    during the request, ensuring
+    profile updates are immediately
+    available for personalization
+end note
+
+@enduml
+----
+
+===== Step 4: Content Filtering and Personalization Resolution
+
+If filters or personalization requests are provided in the request body:
+
+1. **Content Filtering** (if `filters` are provided):
+   * For each filter node, evaluates conditions against the current profile 
and session
+   * Returns filtering results indicating which content variants match
+   * Results are added to the response's `filteringResults` object
+
+2. **Personalization Resolution** (if `personalizations` are provided):
+   * For each personalization request:
+     * Evaluates content filters against the current profile and session 
(which may have been updated by events in Step 3)
+     * Filters use conditions to determine which content variants match
+     * Applies the personalization strategy (e.g., "matching-first", "random", 
etc.)
+   * **Content Selection**: Selects the appropriate content based on:
+     * Filter condition evaluation results
+     * Personalization strategy
+     * Fallback content (if no filters match)
+   * Results are added to both `personalizationResults` (detailed results) and 
`personalizations` (selected content IDs) in the response
+
+Note that personalization uses the profile state after event processing, 
ensuring that events can influence which content is selected.
+
+===== Example: Personalization Resolution
+
+**Example: Request with Events and Personalization**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    },
+    "events": [
+        {
+            "eventType": "view",
+            "scope": "mydigital",
+            "target": {
+                "itemType": "page",
+                "scope": "mydigital",
+                "itemId": "homepage"
+            }
+        }
+    ],
+    "personalizations": [
+        {
+            "id": "homepage-hero",
+            "strategy": "matching-first",
+            "strategyOptions": {
+                "fallback": "default-hero"
+            },
+            "contents": [
+                {
+                    "id": "premium-user-hero",
+                    "content": "Welcome, Premium Member!",
+                    "filters": [
+                        {
+                            "condition": {
+                                "type": "profileSegmentCondition",
+                                "parameterValues": {
+                                    "segments": ["premium-users"]
+                                }
+                            }
+                        }
+                    ]
+                },
+                {
+                    "id": "new-visitor-hero",
+                    "content": "Welcome! Sign up today!",
+                    "filters": [
+                        {
+                            "condition": {
+                                "type": "profilePropertyCondition",
+                                "parameterValues": {
+                                    "propertyName": "properties.firstVisit",
+                                    "comparisonOperator": "exists"
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "requireSegments": true
+}'
+----
+
+**Processing Flow:**
+
+1. **Event Processing**: The `view` event is processed first, which may 
trigger rules that:
+   * Add the profile to segments
+   * Update profile properties
+   * Calculate scores
+
+2. **Personalization Resolution**: After events are processed, personalization 
is evaluated:
+   * For a first-time visitor: The `new-visitor-hero` content matches (because 
`firstVisit` exists)
+   * For a premium user: The `premium-user-hero` content matches (if the 
profile is in the "premium-users" segment)
+   * The `matching-first` strategy selects the first matching content
+
+3. **Response**: The response includes the selected content
+
+**Response:**
+[source,json]
+----
+{
+    "profileId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+    "sessionId": "1234",
+    "processedEvents": 1,
+    "profileSegments": ["new-visitors"],
+    "personalizations": {
+        "homepage-hero": "new-visitor-hero"
+    },
+    "personalizationResults": {
+        "homepage-hero": {
+            "selectedContentId": "new-visitor-hero",
+            "strategy": "matching-first"
+        }
+    }
+}
+----
+
+The `personalizations` object shows the selected content ID for each 
personalization request. The `personalizationResults` provides detailed 
information about the selection process.
+
+[plantuml]
+----
+@startuml
+title Personalization Resolution Flow
+
+ContextEndpoint -> PersonalizationService: personalizeList(profile, session, 
request)
+activate PersonalizationService
+
+loop For each personalization request
+    PersonalizationService -> PersonalizationService: Get content variants
+    
+    loop For each content variant
+        PersonalizationService -> PersonalizationService: Evaluate content 
filters
+        activate PersonalizationService
+        
+        loop For each filter condition
+            PersonalizationService -> ConditionService: Evaluate 
condition\n(profile, session, event history)
+            ConditionService --> PersonalizationService: Match result
+        end
+        
+        alt All filters match
+            PersonalizationService -> PersonalizationService: Content variant 
matches
+        else Filters don't match
+            PersonalizationService -> PersonalizationService: Skip variant
+        end
+        deactivate PersonalizationService
+    end
+    
+    PersonalizationService -> PersonalizationService: Apply 
strategy\n(matching-first, random, etc.)
+    alt Content selected
+        PersonalizationService -> PersonalizationService: Use selected content
+    else No content matches
+        PersonalizationService -> PersonalizationService: Use fallback content
+    end
+end
+
+PersonalizationService --> ContextEndpoint: PersonalizationResult
+deactivate PersonalizationService
+
+note right of PersonalizationService
+    Personalization uses the profile
+    state AFTER event processing,
+    so events can influence
+    content selection
+end note
+
+@enduml
+----
+
+===== Step 5: Response Preparation
+
+The response is built with the following information (based on what was 
requested in the `ContextRequest`):
+
+* **Profile Information**: 
+  * Profile ID (always included)
+  * Profile properties (if `requiredProfileProperties` is specified, with 
`"*"` for all properties)
+  * Profile segments (if `requireSegments` is `true`)
+  * Profile scores (if `requireScores` is `true`)
+  * Anonymous browsing status
+  * Consent information
+* **Session Information**: 
+  * Session ID (if session exists)
+  * Session properties (if `requiredSessionProperties` is specified)
+* **Event Processing Results**: 
+  * Count of processed events (`processedEvents`)
+* **Personalization Results**: 
+  * `personalizations`: Map of personalization ID to selected content IDs
+  * `personalizationResults`: Detailed results for each personalization request
+* **Filtering Results**: 
+  * `filteringResults`: Results of content filtering operations (if `filters` 
were provided)
+* **Tracked Conditions**: 
+  * Conditions that are being tracked for the current source (if not using a 
Persona)
+
+===== Step 6: Cookie Setting
+
+After processing, Apache Unomi sets a cookie in the HTTP response header 
(`Set-Cookie`) with:
+
+[NOTE]
+====
+Cookies are only set for regular profiles, not for Personas. If a `personaId` 
is used in the request, no cookie will be set in the response.
+====
+
+* **Cookie Name**: `context-profile-id` (configurable, default: 
`context-profile-id`)
+* **Cookie Value**: The profile ID (UUID)
+* **Cookie Attributes**:
+  * `Path=/` - Available for all paths on the domain
+  * `Max-Age=31536000` - Valid for 1 year by default (configurable via 
`org.apache.unomi.profile.cookie.maxAgeInSeconds`)
+  * `SameSite=Lax` - CSRF protection
+  * `Secure` - Set if the request is over HTTPS (configurable)
+  * `HttpOnly` - Configurable via `org.apache.unomi.profile.cookie.httpOnly` 
(default: false)
+  * `Domain` - Configurable via `org.apache.unomi.profile.cookie.domain`
+
+This cookie allows the browser to automatically send the profile ID on 
subsequent requests, enabling Apache Unomi to load the existing profile.
+
+===== Example: Cookie Setting
+
+**Example: Cookie in Response Headers**
+
+When you make a request, Apache Unomi sets a cookie in the response. Here's 
what the cookie looks like:
+
+**Response Headers:**
+[source,http]
+----
+HTTP/1.1 200 OK
+Content-Type: application/json;charset=UTF-8
+Set-Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890; Path=/; 
Max-Age=31536000; SameSite=Lax
+----
+
+**Cookie Breakdown:**
+* `context-profile-id` - Cookie name (configurable)
+* `a1b2c3d4-e5f6-7890-abcd-ef1234567890` - Profile ID (UUID)
+* `Path=/` - Available for all paths on the domain
+* `Max-Age=31536000` - Valid for 1 year (31,536,000 seconds)
+* `SameSite=Lax` - CSRF protection
+
+**Subsequent Requests:**
+
+On the next request, the browser automatically includes the cookie:
+
+[source,http]
+----
+# Browser automatically sends cookie
+GET /cxs/context.json?sessionId=1234 HTTP/1.1
+Host: localhost:8181
+X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY
+Cookie: context-profile-id=a1b2c3d4-e5f6-7890-abcd-ef1234567890
+----
+
+Apache Unomi reads the cookie and loads the existing profile, maintaining 
continuity across requests.
+
+===== Step 7: Persistence
+
+Finally, Apache Unomi persists any changes:
+
+* **Profile Persistence**: If the profile was created or updated, it is saved 
to the database
+* **Session Persistence**: If the session was created or updated, it is saved 
to the database
+
+==== First-Time Visitor Flow
+
+For a first-time visitor making their first request:
+
+1. No `context-profile-id` cookie exists
+2. No `profileId` parameter is provided
+3. Apache Unomi generates a new UUID and creates a new profile
+4. If a `sessionId` is provided, a new session is created
+5. The profile ID is returned in a cookie
+6. The browser stores the cookie
+7. On subsequent requests, the browser automatically sends the cookie
+8. Apache Unomi loads the existing profile using the cookie value
+
+===== Complete Example: First-Time Visitor Journey
+
+**Request 1: First Visit (No Cookie)**
+
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=visitor-123 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "homepage",
+        "itemType": "page",
+        "scope": "mydigital"
+    },
+    "events": [
+        {
+            "eventType": "view",
+            "scope": "mydigital",
+            "target": {
+                "itemType": "page",
+                "scope": "mydigital",
+                "itemId": "homepage"
+            }
+        }
+    ],
+    "requiredProfileProperties": ["properties.firstVisit"]
+}'
+----
+
+**Response Headers:**
+[source,http]
+----
+Set-Cookie: context-profile-id=7f8e9d0c-1b2a-3456-7890-abcdef123456; Path=/; 
Max-Age=31536000; SameSite=Lax
+----
+
+**Response Body:**
+[source,json]
+----
+{
+    "profileId": "7f8e9d0c-1b2a-3456-7890-abcdef123456",
+    "sessionId": "visitor-123",
+    "processedEvents": 1,
+    "profileProperties": {
+        "firstVisit": "2024-01-15T10:30:00Z"
+    }
+}
+----
+
+**What Happened:**
+* New profile created with UUID `7f8e9d0c-1b2a-3456-7890-abcdef123456`
+* `firstVisit` property set to current timestamp
+* New session created with ID `visitor-123`
+* `sessionCreated` event sent (triggers any matching rules)
+* `profileUpdated` event sent (triggers any matching rules)
+* `view` event processed (may have triggered rules)
+* Cookie set in response header
+* Profile and session saved to database
+
+**Request 2: Second Visit (Cookie Present)**
+
+[source,bash]
+----
+# Browser automatically includes the cookie from Request 1
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=visitor-123 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=7f8e9d0c-1b2a-3456-7890-abcdef123456" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "product-page",
+        "itemType": "page",
+        "scope": "mydigital"
+    },
+    "events": [
+        {
+            "eventType": "view",
+            "scope": "mydigital",
+            "target": {
+                "itemType": "product",
+                "scope": "mydigital",
+                "itemId": "product-456"
+            }
+        }
+    ],
+    "requiredProfileProperties": ["properties.firstVisit", 
"properties.pageViewCount"]
+}'
+----
+
+**Response:**
+[source,json]
+----
+{
+    "profileId": "7f8e9d0c-1b2a-3456-7890-abcdef123456",
+    "sessionId": "visitor-123",
+    "processedEvents": 1,
+    "profileProperties": {
+        "firstVisit": "2024-01-15T10:30:00Z",
+        "pageViewCount": 2
+    }
+}
+----
+
+**What Happened:**
+* Existing profile loaded using cookie value 
`7f8e9d0c-1b2a-3456-7890-abcdef123456`
+* Existing session loaded using `visitor-123`
+* `view` event processed, which may have triggered a rule that incremented 
`pageViewCount`
+* Cookie refreshed in response (expiration extended)
+* Updated profile saved to database
+
+The profile is now being tracked across multiple requests, and any rules 
triggered by events can build up a profile of the visitor's behavior.
+
+[plantuml]
+----
+@startuml
+title First-Time Visitor vs Returning Visitor Flow
+
+== First Request (First-Time Visitor) ==
+
+Client -> ContextEndpoint: POST /context.json\n+ X-Unomi-Api-Key\n+ 
sessionId=1234
+activate ContextEndpoint
+
+ContextEndpoint -> RestServiceUtils: initEventsRequest()
+RestServiceUtils -> HttpServletRequest: getCookie("context-profile-id")
+HttpServletRequest --> RestServiceUtils: null (no cookie)
+RestServiceUtils -> RestServiceUtils: profileId = null
+RestServiceUtils -> RestServiceUtils: createNewProfile(null)
+RestServiceUtils -> RestServiceUtils: Generate UUID: "abc-123-def"
+RestServiceUtils -> ProfileService: Create profile with firstVisit
+RestServiceUtils -> EventService: Send profileUpdated event
+RestServiceUtils -> ProfileService: Create session(sessionId, profile)
+RestServiceUtils -> EventService: Send sessionCreated event
+RestServiceUtils --> ContextEndpoint: Profile and session created
+
+ContextEndpoint -> RestServiceUtils: finalizeEventsRequest()
+RestServiceUtils -> ProfileService: Save profile
+RestServiceUtils -> ProfileService: Save session
+RestServiceUtils -> HttpUtils: Generate cookie: 
"context-profile-id=abc-123-def"
+RestServiceUtils -> HttpServletResponse: setHeader("Set-Cookie", cookie)
+RestServiceUtils --> ContextEndpoint: Cookie set
+
+ContextEndpoint --> Client: ContextResponse\n+ Set-Cookie: 
context-profile-id=abc-123-def
+deactivate ContextEndpoint
+
+Client -> Client: Browser stores cookie
+
+== Subsequent Request (Returning Visitor) ==
+
+Client -> ContextEndpoint: POST /context.json\n+ X-Unomi-Api-Key\n+ 
sessionId=1234\n+ Cookie: context-profile-id=abc-123-def
+activate ContextEndpoint
+
+ContextEndpoint -> RestServiceUtils: initEventsRequest()
+RestServiceUtils -> HttpServletRequest: getCookie("context-profile-id")
+HttpServletRequest --> RestServiceUtils: "abc-123-def"
+RestServiceUtils -> ProfileService: load("abc-123-def")
+ProfileService --> RestServiceUtils: Existing profile
+RestServiceUtils -> ProfileService: loadSession("1234")
+ProfileService --> RestServiceUtils: Existing session
+RestServiceUtils --> ContextEndpoint: Profile and session loaded
+
+ContextEndpoint -> RestServiceUtils: finalizeEventsRequest()
+RestServiceUtils -> HttpUtils: Generate cookie: 
"context-profile-id=abc-123-def"
+RestServiceUtils -> HttpServletResponse: setHeader("Set-Cookie", cookie)
+RestServiceUtils --> ContextEndpoint: Cookie refreshed
+
+ContextEndpoint --> Client: ContextResponse\n+ Set-Cookie: 
context-profile-id=abc-123-def\n(refreshed expiration)
+deactivate ContextEndpoint
+
+@enduml
+----
+
+==== Complete Example: Full Request Flow
+
+Here's a complete example showing all aspects of a context request:
+
+**Request:**
+[source,bash]
+----
+curl -X POST http://localhost:8181/cxs/context.json?sessionId=example-session \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
+-H "Cookie: context-profile-id=existing-profile-uuid" \
+-H "Content-Type: application/json" \
+-d '{
+    "source": {
+        "itemId": "checkout-page",
+        "itemType": "page",
+        "scope": "mydigital"
+    },
+    "events": [
+        {
+            "eventType": "view",
+            "scope": "mydigital",
+            "target": {
+                "itemType": "page",
+                "scope": "mydigital",
+                "itemId": "checkout-page"
+            }
+        }
+    ],
+    "personalizations": [
+        {
+            "id": "checkout-banner",
+            "strategy": "matching-first",
+            "strategyOptions": {
+                "fallback": "default-banner"
+            },
+            "contents": [
+                {
+                    "id": "discount-banner",
+                    "content": "Use code SAVE10 for 10% off!",
+                    "filters": [
+                        {
+                            "condition": {
+                                "type": "profileSegmentCondition",
+                                "parameterValues": {
+                                    "segments": ["frequent-buyers"]
+                                }
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    ],
+    "requiredProfileProperties": ["*"],
+    "requireSegments": true,
+    "requireScores": true
+}'
+----
+
+**Processing Steps:**
+
+1. **Tenant Resolution**: Resolved from `X-Unomi-Api-Key` header
+2. **Profile Loading**: Loaded existing profile `existing-profile-uuid` from 
cookie
+3. **Session Loading**: Loaded existing session `example-session`
+4. **Event Processing**: 
+   * `view` event validated and processed
+   * Rules executed (may update profile, segments, scores)
+   * Profile updated if rules triggered changes
+5. **Personalization**: 
+   * Evaluated using updated profile state
+   * Selected content based on segment membership
+6. **Response Building**: Constructed with all requested data
+7. **Cookie Setting**: Cookie refreshed in response header
+8. **Persistence**: Profile and session saved if updated
+
+**Response:**
+[source,json]
+----
+{
+    "profileId": "existing-profile-uuid",
+    "sessionId": "example-session",
+    "processedEvents": 1,
+    "profileProperties": {
+        "firstName": "John",
+        "lastName": "Doe",
+        "email": "[email protected]",
+        "pageViewCount": 15
+    },
+    "profileSegments": ["frequent-buyers", "premium-customers"],
+    "profileScores": {
+        "loyalty": 85,
+        "engagement": 92
+    },
+    "personalizations": {
+        "checkout-banner": "discount-banner"
+    },
+    "personalizationResults": {
+        "checkout-banner": {
+            "selectedContentId": "discount-banner",
+            "strategy": "matching-first"
+        }
+    }
+}
+----
+
+**Response Headers:**
+[source,http]
+----
+Set-Cookie: context-profile-id=existing-profile-uuid; Path=/; 
Max-Age=31536000; SameSite=Lax
+----
+
+This example demonstrates how a single request can:
+* Load an existing profile and session
+* Process events that trigger rules
+* Update the profile based on rule execution
+* Resolve personalization using the updated profile state
+* Return comprehensive profile data
+* Maintain tracking via cookies
+
+==== Summary
+
+A single request to `/cxs/context.json` or `/cxs/context.js` performs the 
following operations in sequence:
+
+1. **Tenant Resolution** (mandatory in 3.1+): Identifies which tenant's data 
to use
+2. **Profile/Session Management**: Creates or loads profiles and sessions 
automatically
+3. **Event Processing**: Processes events and executes matching rules 
(synchronously)
+4. **Content Filtering**: Evaluates filter conditions (if `filters` are 
provided)
+5. **Personalization**: Resolves personalization requests (if 
`personalizations` are provided)
+6. **Response Building**: Constructs the response with requested data 
(properties, segments, scores, etc.)
+7. **Persistence**: Saves any changes to the database (profile and session)
+8. **Cookie Setting**: Sets the profile ID cookie for future requests
+
+This integrated approach ensures that:
+* Profiles and sessions are always available, even for first-time visitors
+* Events immediately trigger rules, updating profiles in real-time
+* Personalization uses the most up-to-date profile state
+* The browser automatically tracks the visitor across requests via cookies
+
+==== Configuration
+
+The following configuration properties control profile tracking behavior:
+
+* `org.apache.unomi.profile.cookie.name` - Cookie name (default: 
`context-profile-id`)
+* `org.apache.unomi.profile.cookie.maxAgeInSeconds` - Cookie expiration time 
(default: `31536000` = 1 year)
+* `org.apache.unomi.profile.cookie.httpOnly` - Whether cookie is HTTP-only 
(default: `false`)
+* `org.apache.unomi.profile.cookie.domain` - Cookie domain (optional)
+
+These can be configured in the `org.apache.unomi.web.cfg` configuration file.
diff --git a/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc 
b/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc
index d489912b1..1b908c206 100644
--- a/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc
+++ b/manual/src/main/asciidoc/jsonSchema/json-schema-api.adoc
@@ -16,6 +16,8 @@
 
 The JSON schema endpoints are private, so the user has to be authenticated to 
manage the JSON schema in Unomi.
 
+IMPORTANT: JSON schema endpoints require tenant authentication using Basic 
Auth with `tenantId:privateKey`. Only the Tenant API (`/cxs/tenants`) uses 
system administrator authentication (`karaf:karaf`).
+
 ==== List existing schemas
 
 The REST endpoint GET `{{url}}/cxs/jsonSchema` allows to get all ids of 
available schemas and subschemas.
@@ -62,12 +64,14 @@ Example:
 [source]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/jsonSchema/query' \
--u 'karaf:karaf'
+--user 'TENANT_ID:PRIVATE_KEY' \
 --header 'Content-Type: text/plain' \
---header 'Cookie: context-profile-id=0f2fbca8-c242-4e6d-a439-d65fcbf0f0a8' \
 --data-raw 'https://unomi.apache.org/schemas/json/event/1-0-0'
 ----
 
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key. You can obtain these when creating a tenant via the Tenant API 
(which requires system administrator authentication).
+
+[[_create_update_json_schema]]
 ==== Create / update a JSON schema to validate an event
 
 It’s possible to add or update JSON schema by calling the endpoint `POST 
{{url}}/cxs/jsonSchema` with the JSON schema in the payload of the request.
@@ -78,9 +82,8 @@ Example of creation:
 [source]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/jsonSchema' \
--u 'karaf:karaf' \
+--user 'TENANT_ID:PRIVATE_KEY' \
 --header 'Content-Type: application/json' \
---header 'Cookie: context-profile-id=0f2fbca8-c242-4e6d-a439-d65fcbf0f0a8' \
 --data-raw '{
     "$id": "https://vendor.test.com/schemas/json/events/dummy/1-0-0";,
     "$schema": "https://json-schema.org/draft/2019-09/schema";,
@@ -116,12 +119,13 @@ Example:
 [source]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/jsonSchema/delete' \
--u 'karaf:karaf' \
+--user 'TENANT_ID:PRIVATE_KEY' \
 --header 'Content-Type: text/plain' \
---header 'Cookie: context-profile-id=0f2fbca8-c242-4e6d-a439-d65fcbf0f0a8' \
 --data-raw 'https://vendor.test.com/schemas/json/events/dummy/1-0-0'
 ----
 
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key.
+
 ==== Error Management
 
 When calling an endpoint with invalid data, such as an invalid value for the 
*sessionId* property in the contextRequest object or eventCollectorRequest 
object, the server would respond with a 400 error code and the message *Request 
rejected by the server because: Invalid received data*.
diff --git a/manual/src/main/asciidoc/jsonSchema/json-schema-develop.adoc 
b/manual/src/main/asciidoc/jsonSchema/json-schema-develop.adoc
index 34e4b18cf..ece986438 100644
--- a/manual/src/main/asciidoc/jsonSchema/json-schema-develop.adoc
+++ b/manual/src/main/asciidoc/jsonSchema/json-schema-develop.adoc
@@ -41,14 +41,14 @@ Doing so will output logs similar to this:
 
 ==== validateEvent endpoint
 
-A dedicated Admin endpoint (requires authentication), accessible at: 
`cxs/jsonSchema/validateEvent`, was created to validate events against JSON 
Schemas loaded in Apache Unomi.
+A dedicated endpoint (requires tenant authentication), accessible at: 
`cxs/jsonSchema/validateEvent`, was created to validate events against JSON 
Schemas loaded in Apache Unomi.
 
 For example, sending an event not matching a schema:
 [source]
 ----
 curl --request POST \
   --url http://localhost:8181/cxs/jsonSchema/validateEvent \
-  --user karaf:karaf \  
+  --user 'TENANT_ID:PRIVATE_KEY' \
   --header 'Content-Type: application/json' \
   --data '{
     "eventType": "no-event",
@@ -81,14 +81,14 @@ towards the incorrect property:
 
 ==== validateEvents endpoint
 
-A dedicated Admin endpoint (requires authentication), accessible at: 
`cxs/jsonSchema/validateEvents`, was created to validate a list of event at 
once against JSON Schemas loaded in Apache Unomi.
+A dedicated endpoint (requires tenant authentication), accessible at: 
`cxs/jsonSchema/validateEvents`, was created to validate a list of event at 
once against JSON Schemas loaded in Apache Unomi.
 
 For example, sending a list of event not matching a schema:
 [source]
 ----
 curl --request POST \
   --url http://localhost:8181/cxs/jsonSchema/validateEvents \
-  --user karaf:karaf \
+  --user 'TENANT_ID:PRIVATE_KEY' \
   --header 'Content-Type: application/json' \
   --data '[{
     "eventType": "view",
diff --git a/manual/src/main/asciidoc/privacy.adoc 
b/manual/src/main/asciidoc/privacy.adoc
index f35dc0cc4..bbde3c398 100644
--- a/manual/src/main/asciidoc/privacy.adoc
+++ b/manual/src/main/asciidoc/privacy.adoc
@@ -69,9 +69,12 @@ session data will also be detached from the current profile 
and anonymized.
 
 [source]
 ----
-curl -X DELETE 
http://localhost:8181/cxs/privacy/profiles/{profileID}?withData=false --user 
karaf:karaf
+curl -X DELETE 
http://localhost:8181/cxs/privacy/profiles/{profileID}?withData=false \
+--user "TENANT_ID:PRIVATE_KEY"
 ----
 
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key. Only the Tenant API (`/cxs/tenants`) uses system administrator 
authentication (`karaf:karaf`).
+
 where `{profileID}` must be replaced by the actual identifier of a profile
 and the `withData` specifies whether the data associated with the profile must 
be anonymized or not
 
diff --git a/manual/src/main/asciidoc/recipes.adoc 
b/manual/src/main/asciidoc/recipes.adoc
index a245375c4..5d21c2d4d 100644
--- a/manual/src/main/asciidoc/recipes.adoc
+++ b/manual/src/main/asciidoc/recipes.adoc
@@ -82,6 +82,7 @@ Here is an example that will retrieve all the session and 
profile properties, as
 ----
 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",
@@ -143,7 +144,7 @@ Let's go into more detail about the preferred way to update 
a profile. Let's con
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/rules \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 --data-raw '{
   "metadata": {
@@ -199,7 +200,7 @@ We will start by creating a scope called "example" scope:
 [source]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/scopes' \
--u 'karaf:karaf' \
+--user "TENANT_ID:PRIVATE_KEY" \
 --header 'Content-Type: application/json' \
 --data-raw '{
 "itemId": "example",
@@ -207,12 +208,14 @@ curl --location --request POST 
'http://localhost:8181/cxs/scopes' \
 }'
 ----
 
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key. Only the Tenant API (`/cxs/tenants`) uses system administrator 
authentication (`karaf:karaf`).
+
 The next step consist in creating a JSON Schema to validate our event.
 
 [source]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/jsonSchema' \
--u 'karaf:karaf' \
+--user "TENANT_ID:PRIVATE_KEY" \
 --header 'Content-Type: application/json' \
 --data-raw '{
     "$id": 
"https://unomi.apache.org/schemas/json/events/contactInfoSubmitted/1-0-0";,
@@ -266,6 +269,7 @@ Finally, send the `contactInfoSubmitted` event using a 
request similar to this o
 ----
 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":[
@@ -297,7 +301,7 @@ The event we just submitted can be retrieved using the 
following request:
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/events/search \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 --data-raw '{
   "offset" : 0,
@@ -346,7 +350,7 @@ that looks something like this (and 
https://unomi.apache.org/rest-api-doc/#17681
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/events/search \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 --data-raw '{
   "offset" : 0,
@@ -378,7 +382,7 @@ on the Apache Unomi server.
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/rules \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 --data-raw '{
   "metadata": {
@@ -413,7 +417,7 @@ structure. Here's an example of a profile search with a 
Query object:
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/profiles/search \
---user karaf:karaf \
+--user "TENANT_ID:PRIVATE_KEY" \
 -H "Content-Type: application/json" \
 --data-raw '{
   "text" : "unomi",
diff --git a/manual/src/main/asciidoc/request-examples.adoc 
b/manual/src/main/asciidoc/request-examples.adoc
index 25d0774ed..69af6fe01 100644
--- a/manual/src/main/asciidoc/request-examples.adoc
+++ b/manual/src/main/asciidoc/request-examples.adoc
@@ -84,6 +84,19 @@ curl -X POST http://localhost:8181/cxs/scopes \
 
 TIP: The scope creation response will include a public API key that you should 
save for use with the public APIs.
 
+[IMPORTANT]
+====
+**Tenant Resolution (Version 3.1+)**
+
+Starting with Apache Unomi 3.1, tenant resolution is mandatory for all 
requests to `/cxs/context.json` and `/cxs/eventcollector` endpoints. The tenant 
must be identified before Apache Unomi can process the request. This is done 
automatically when you use:
+
+* **Public API Key** (recommended for public endpoints): The `X-Unomi-Api-Key` 
header with a public API key automatically resolves the tenant
+* **Private API Key**: Basic authentication with `tenantId:privateApiKey` 
resolves the tenant from the credentials
+* **Tenant ID Header**: When using JAAS authentication, the 
`X-Unomi-Tenant-Id` header can be used
+
+All examples in this document use the `X-Unomi-Api-Key` header with a public 
API key, which is the recommended approach for public-facing applications. See 
<<_how_profile_tracking_works,How profile tracking works>> for complete details 
about tenant resolution.
+====
+
 NOTE: In all the examples below, replace:
 - `YOUR_TENANT_ID` with `mytenant`
 - `YOUR_PRIVATE_API_KEY` with your actual private API key
@@ -131,6 +144,7 @@ For multi-line curl requests using heredoc syntax 
(`<<'EOF'`), you can still use
 # With heredoc
 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" \
 -d @- <<'EOF' | jq '.'
 {
     "source": {
@@ -182,7 +196,8 @@ You can retrieve a context using curl like this :
 
 [source]
 ----
-curl http://localhost:8181/cxs/context.js?sessionId=1234
+curl http://localhost:8181/cxs/context.js?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY"
 ----
 
 This will retrieve a JavaScript script that contains a `cxs` object that 
contains the context with the current user
@@ -208,7 +223,8 @@ If you prefer to retrieve a pure JSON object, you can 
simply use a request forme
 
 [source]
 ----
-curl http://localhost:8181/cxs/context.json?sessionId=1234
+curl http://localhost:8181/cxs/context.json?sessionId=1234 \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY"
 ----
 
 The same automatic profile and session creation behavior applies to 
`context.json` requests. See <<_how_profile_tracking_works,How profile tracking 
works>> for complete details.
@@ -226,12 +242,13 @@ scores
 ----
 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" \
 -d @- <<'EOF'
 {
     "source": {
         "itemId":"homepage",
         "itemType":"page",
-        "scope":"example"
+        "scope":"mydigital"
     },
     "requiredProfileProperties":["*"],
     "requiredSessionProperties":["*"],
@@ -255,25 +272,26 @@ illustrated in the following example:
 ----
 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" \
 -d @- <<'EOF'
 {
     "source":{
         "itemId":"homepage",
         "itemType":"page",
-        "scope":"example"
+        "scope":"mydigital"
     },
     "events":[
         {
             "eventType":"view",
-            "scope": "example",
+            "scope": "mydigital",
             "source":{
                 "itemType": "site",
-                "scope":"example",
+                "scope":"mydigital",
                 "itemId": "mysite"
             },
             "target":{
                 "itemType":"page",
-                "scope":"example",
+                "scope":"mydigital",
                 "itemId":"homepage",
                 "properties":{
                     "pageInfo":{
@@ -301,21 +319,22 @@ respond quickly and minimize network traffic. Here is an 
example of using this s
 ----
 curl -X POST http://localhost:8181/cxs/eventcollector \
 -H "Content-Type: application/json" \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
 -d @- <<'EOF'
 {
     "sessionId" : "1234",
     "events":[
         {
             "eventType":"view",
-            "scope": "example",
+            "scope": "mydigital",
             "source":{
                 "itemType": "site",
-                "scope":"example",
+                "scope":"mydigital",
                 "itemId": "mysite"
             },
             "target":{
                 "itemType":"page",
-                "scope":"example",
+                "scope":"mydigital",
                 "itemId":"homepage",
                 "properties":{
                     "pageInfo":{
@@ -334,8 +353,8 @@ to send additional events.
 
 ==== Where to go from here
 
-* You can find more <<Useful Apache Unomi URLs,useful Apache Unomi URLs>> that 
can be used in the same way as the above examples.
-* Read the <<Twitter sample,Twitter sample>> documentation that contains a 
detailed example of how to integrate with Apache Unomi.
+* You can find more <<_useful_apache_unomi_urls,useful Apache Unomi URLs>> 
that can be used in the same way as the above examples.
+* Read the <<_twitter_sample,Twitter sample>> documentation that contains a 
detailed example of how to integrate with Apache Unomi.
 
 === Public API Examples
 
@@ -1347,7 +1366,8 @@ To remove the Groovy action:
 [source]
 ----
 curl -X DELETE http://localhost:8181/cxs/groovyActions/extractBirthday \
---user "YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY"
+--user "YOUR_TENANT_ID:YOUR_PRIVATE_API_KEY" \
+-H "Content-Type: application/json"
 ----
 
 NOTE: This will only remove the Groovy script. You'll need to separately 
delete the action definition and rule if desired.
@@ -1379,7 +1399,7 @@ To use the explain parameter, you must have one of the 
following roles:
 [source]
 ----
 curl -X POST 
http://localhost:8181/cxs/context.json?sessionId=1234&explain=true \
---user "karaf:karaf" \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
 -H "Content-Type: application/json" \
 -d @- <<'EOF'
 {
@@ -1399,7 +1419,7 @@ EOF
 [source]
 ----
 curl -X POST http://localhost:8181/cxs/eventcollector?explain=true \
---user "karaf:karaf" \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
 -H "Content-Type: application/json" \
 -d @- <<'EOF'
 {
@@ -1581,7 +1601,7 @@ This example demonstrates using the explain parameter to 
understand how personal
 [source]
 ----
 curl -X POST 
http://localhost:8181/cxs/context.json?sessionId=1234&explain=true \
---user "karaf:karaf" \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
 -H "Content-Type: application/json" \
 -d @- <<'EOF'
 {
diff --git a/manual/src/main/asciidoc/samples/twitter-sample.adoc 
b/manual/src/main/asciidoc/samples/twitter-sample.adoc
index 918693fce..7ac2c65ec 100644
--- a/manual/src/main/asciidoc/samples/twitter-sample.adoc
+++ b/manual/src/main/asciidoc/samples/twitter-sample.adoc
@@ -215,6 +215,7 @@ Here is an example of a filter request:
 ----
 curl --location --request POST 'http://localhost:8181/cxs/context.json' \
 --header 'Content-Type: application/json' \
+--header 'X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY' \
 --header 'Cookie: JSESSIONID=48C8AFB3E18B8E3C93C2F4D5B7BD43B7; 
context-profile-id=01060c4c-a055-4c8f-9692-8a699d0c434a' \
 --data-raw '{
     "source": null,
@@ -284,6 +285,7 @@ Here is an example of a `personalizations` request:
 ----
 curl --location --request POST 'http://localhost:8181/cxs/context.json' \
 --header 'Content-Type: application/json' \
+--header 'X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY' \
 --header 'Cookie: JSESSIONID=48C8AFB3E18B8E3C93C2F4D5B7BD43B7; 
context-profile-id=01060c4c-a055-4c8f-9692-8a699d0c434a' \
 --data-raw '{
     "source": null,
diff --git a/manual/src/main/asciidoc/tutorial.adoc 
b/manual/src/main/asciidoc/tutorial.adoc
index b239c3ab3..cc5d89abe 100644
--- a/manual/src/main/asciidoc/tutorial.adoc
+++ b/manual/src/main/asciidoc/tutorial.adoc
@@ -95,7 +95,7 @@ You might notice the `scope` used in the snippet. All events 
sent to Unomi must
 [source,shell]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/scopes' \
-  --header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+  --user "TENANT_ID:PRIVATE_KEY" \
   --header 'Content-Type: application/json' \
   --data-raw '{
     "itemId": "unomi-tracker-test",
@@ -106,7 +106,7 @@ curl --location --request POST 
'http://localhost:8181/cxs/scopes' \
   }'
 ----
 
-The authorization is the default username/password for the REST API, which is 
`karaf:karaf` and you that should definitely be changed as soon as possible by 
modifying the `etc/users.properties` file.
+NOTE: Replace `TENANT_ID` and `PRIVATE_KEY` with your actual tenant ID and 
private API key. Only the Tenant API (`/cxs/tenants`) uses system administrator 
authentication (`karaf:karaf`). The default `karaf:karaf` credentials should be 
changed as soon as possible by modifying the `etc/users.properties` file.
 
 ==== Using tracker in your own JavaScript projects
 
@@ -184,7 +184,7 @@ There are multiple ways to view the events that were 
received. For example, you
 [source,shell]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/events/search' \
-  --header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+  --user "TENANT_ID:PRIVATE_KEY" \
   --header 'Content-Type: application/json' \
   --data-raw '{
     "sortby" : "timeStamp:desc",
@@ -221,7 +221,7 @@ You could also retrieve the profile details using the REST 
API by using a reques
 [source,shell]
 ----
 curl --location --request GET 
'http://localhost:8181/cxs/profiles/PROFILE_UUID' \
---header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+--user "TENANT_ID:PRIVATE_KEY"
 ----
 
 ==== Adding a rule
@@ -233,7 +233,7 @@ In this example we will simply setup a basic rule that will 
react to the `view`
 [source,shell]
 ----
 curl --location --request POST 'http://localhost:8181/cxs/rules' \
---header 'Authorization: Basic a2FyYWY6a2FyYWY=' \
+--user "TENANT_ID:PRIVATE_KEY" \
 --header 'Content-Type: application/json' \
 --data-raw '{
     "metadata": {
diff --git a/manual/src/main/asciidoc/updating-events.adoc 
b/manual/src/main/asciidoc/updating-events.adoc
index e883c7b5b..3ef158b9e 100644
--- a/manual/src/main/asciidoc/updating-events.adoc
+++ b/manual/src/main/asciidoc/updating-events.adoc
@@ -38,7 +38,7 @@ Here is an example of a request contains the `itemdId`
 ----
 curl -X POST http://localhost:8181/cxs/context.json \
 -H "Content-Type: application/json" \
--H "Authorization: Basic {base64-encoded-credentials}" \
+--user "TENANT_ID:PRIVATE_KEY" \
 -d @- <<'EOF'
 {
     "events":[
@@ -65,6 +65,7 @@ this can be achieved by adding `"raiseEventOnlyOnce": false` 
to the rule definit
 ----
 curl -X POST http://localhost:8181/cxs/context.json \
 -H "Content-Type: application/json" \
+-H "X-Unomi-Api-Key: YOUR_PUBLIC_API_KEY" \
 -d @- <<'EOF'
 {
   "metadata": {
diff --git a/pom.xml b/pom.xml
index 66c14101e..026c1a15e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -160,7 +160,7 @@
         <jacoco-maven-plugin.version>0.8.13</jacoco-maven-plugin.version>
         <maven-clean-plugin.version>3.1.0</maven-clean-plugin.version>
         <frontend-maven-plugin.version>1.12.1</frontend-maven-plugin.version>
-        
<asciidoctor-maven-plugin.version>3.1.1</asciidoctor-maven-plugin.version>
+        
<asciidoctor-maven-plugin.version>3.2.0</asciidoctor-maven-plugin.version>
         <checksum-maven-plugin.version>1.7</checksum-maven-plugin.version>
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
         <maven-checkstyle-plugin.version>2.13</maven-checkstyle-plugin.version>

Reply via email to