Skip to Content
SessionsSession Metadata

Session Metadata

Session metadata is a flexible, client-controlled JSON object that enables custom tracking and analytics for your conversations.

What is Metadata?

Metadata is a flexible JSON object that clients can use to store custom information about a session.

Characteristics

Client Ownership:

  • Clients decide what data to store
  • No schema validation or restrictions
  • Can store any JSON-serializable values

Type Support:

  • Strings: "mobile-app", "user@example.com"
  • Numbers: 42, 3.14, 1234567890
  • Booleans: true, false
  • Arrays: ["tag1", "tag2"], [1, 2, 3]
  • Nested objects: {"key": "value", "nested": {"deep": true}}
  • Null: Used to delete fields in PATCH operations

Shallow Merge Updates:

  • PATCH operations merge at top level only
  • Existing keys are overwritten
  • New keys are added
  • Unmentioned keys are preserved
  • Null values delete fields: Set field to null to remove it

Persistence:

  • Stored in session database record
  • Returned in all session responses
  • Preserved through session lifecycle
  • Available for filtering and analysis

Common Use Cases

1. Source Tracking

Track where the session originated:

{
  metadata: {
    source: "website",
    page_url: "https://example.com/support",
    referrer: "https://google.com/search?q=help",
    utm_campaign: "spring_2025",
    utm_source: "email",
  },
}

2. User Information

Store non-sensitive user context:

{
  metadata: {
    user_segment: "premium",
    account_age_days: 45,
    preferred_language: "en-US",
    timezone: "America/New_York",
  },
}

3. Session Analytics

Track interaction metrics:

{
  metadata: {
    interaction_count: 5,
    feedback_submitted: true,
    survey_shown: false,
    session_rating: 4,
    helpful_responses: 3,
  },
}

4. Widget State

Maintain UI state for resumption:

{
  metadata: {
    window_position: "bottom-right",
    theme: "light",
    collapsed: false,
    last_input: "How do I...",
    draft_message: "",
  },
}

5. Business Logic

Custom application data:

{
  metadata: {
    order_id: "ORD-12345",
    product_category: "electronics",
    support_tier: "priority",
    assigned_agent_id: "agent-789",
    escalation_required: false,
  },
}

6. Complex Data Structures

Store rich, structured data:

{
  metadata: {
    // String values
    userId: "user@example.com",
    source: "mobile-app",

    // Number values
    sessionDuration: 342.5,
    pageViews: 15,
    timestamp: 1234567890,

    // Boolean values
    authenticated: true,
    premiumUser: false,

    // Array values
    tags: ["support", "billing", "urgent"],
    visitedPages: ["/home", "/products", "/checkout"],
    scores: [4.5, 4.8, 5.0],

    // Nested object values
    deviceInfo: {
      type: "mobile",
      os: "iOS",
      version: "17.2",
      screenResolution: {
        width: 1920,
        height: 1080,
      },
    },
    preferences: {
      theme: "dark",
      language: "en-US",
      notifications: {
        email: true,
        push: false,
        sms: false,
      },
    },
    analytics: {
      campaign: "summer-sale",
      medium: "email",
      source: "newsletter",
      conversionValue: 149.99,
    },
  },
}

7. Tag-Based Content Filtering

Control which knowledge base entries are accessible during queries using tags. The tags field supports two modes:

Simple Mode (Array): Filter using a list of tags with AND/OR logic

{
  "tags": [
    "premium",
    "v2"
  ],
  "tagFilterMode": "OR"
}

All queries in this session will only retrieve KB entries that have tag “premium” OR “v2”. Entries with no tags are always included (global content).

Expression Mode (String): Advanced filtering with complex logic

{
  "tags": "(tier1,tier2,tier3),(capability-a+capability-b+capability-c)"
}

This expression means: Entry must have (tier1 OR tier2 OR tier3) OR (capability-a AND capability-b AND capability-c).

Expression Syntax:

  • , (comma) - OR operator: Match entries with ANY of the tags
  • + (plus) - AND operator: Match entries with ALL of the tags
  • @ (at sign) - SUBSET operator: Match entries where ALL entry tags are in the specified set
  • () (parentheses) - Grouping: Control evaluation order

Precedence (highest to lowest): @ (subset) > + (and) > , (or)

Reserved Characters:

  • , - OR operator
  • + - AND operator
  • @ - SUBSET operator
  • ( - Left parenthesis (grouping)
  • ) - Right parenthesis (grouping)
  • - Whitespace (ignored, used for readability)

These characters have special meaning in tag expressions and cannot be used in tag names. Tag names are any sequence of characters that don’t include these reserved characters.

More Examples:

{
  "tags": "admin+(read,write)"
}

Translates to: Entry must have tag “admin” AND (tag “read” OR tag “write”)

Matches: ["admin", "read"], ["admin", "write"], ["admin", "read", "write"]

Does NOT match: ["admin"] (missing read/write), ["read", "write"] (missing admin)


{
  "tags": "premium,(basic+verified)"
}

Translates to: Entry must have tag “premium” OR (tag “basic” AND tag “verified”)

Matches: ["premium"], ["basic", "verified"]

Does NOT match: ["basic"] (missing verified), ["verified"] (missing basic)


{
  "tags": "(region-us,region-eu)+(v2,v3)"
}

Translates to: Entry must have (tag “region-us” OR tag “region-eu”) AND (tag “v2” OR tag “v3”)

Matches: ["region-us", "v2"], ["region-eu", "v3"], ["region-us", "region-eu", "v2", "v3"]

Does NOT match: ["region-us"] (missing version), ["v2"] (missing region)


{
  "tags": "(entitle-a@entitle-b@entitle-c),no-entitlement-required"
}

Translates to: ALL entry tags must be in the set {entitle-a, entitle-b, entitle-c} OR entry has tag “no-entitlement-required”

Use case: User has entitlements {entitle-a, entitle-b, entitle-c}. Content tagged with required entitlements will only match if user has all those requirements.

Matches:

  • ["entitle-a"] - user has this entitlement
  • ["entitle-a", "entitle-b"] - user has both
  • ["no-entitlement-required"] - public content
  • [] - untagged content (always accessible)

Does NOT match:

  • ["entitle-x"] - user doesn’t have this entitlement
  • ["entitle-a", "entitle-x"] - user doesn’t have entitle-x

Key Points:

  • Untagged KB entries (no tags or empty tags array) are always included regardless of filtering
  • Use array mode for simple OR/AND filtering
  • Use string mode for complex multi-level expressions
  • Use @ operator for entitlement/capability checking where content specifies requirements
  • Perfect for role-based access control, tiered content, and multi-tenant scenarios

Deleting Metadata Fields

Use null values to delete fields:

// Delete metadata fields by setting them to null
const sessionId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const experienceId = "550e8400-e29b-41d4-a716-446655440000";

await fetch(
  `https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/metadata?experienceId=${experienceId}`,
  {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer YOUR_API_KEY",
    },
    body: JSON.stringify({
      // Delete fields by setting to null
      temporaryFlag: null,
      oldPageUrl: null,

      // Update or add other fields normally
      currentPageUrl: "https://example.com/new-page",
      lastUpdated: new Date().toISOString(),
    }),
  },
);

Example:

Initial metadata:

{
  "temporaryFlag": true,
  "sessionStartTime": 1234567890,
  "pageUrl": "https://example.com/page1"
}

PATCH request:

{
  "temporaryFlag": null,
  "pageUrl": "https://example.com/page2"
}

Result:

{
  "sessionStartTime": 1234567890,
  "pageUrl": "https://example.com/page2"
}

Updating Metadata

Use the PATCH endpoint for updates:

// Update session metadata (shallow merge)
const sessionId = "session-uuid";
const params = new URLSearchParams({
  experienceId: "your-experience-id",
});

const response = await fetch(
  `https://nexus-api.uat.knowbl.com/api/v2/sessions/${sessionId}/metadata?${params}`,
  {
    method: "PATCH",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer YOUR_ACCESS_TOKEN",
    },
    body: JSON.stringify({
      page_url: "https://example.com/support",
      interaction_count: 5,
    }),
  },
);

const updated = await response.json();
console.log("Metadata updated:", updated.metadata);

Shallow Merge Behavior

Initial Metadata:

{
  "source": "website",
  "page_url": "https://example.com/home",
  "user_segment": "free"
}

PATCH Request:

{
  "page_url": "https://example.com/support",
  "interaction_count": 1
}

Resulting Metadata:

{
  "source": "website",
  "page_url": "https://example.com/support",
  "user_segment": "free",
  "interaction_count": 1
}

Key Points:

  • source and user_segment are preserved (not in PATCH body)
  • page_url is overwritten (in PATCH body)
  • interaction_count is added (new key)

Best Practices

Do:

  • Use metadata for client-specific tracking needs
  • Store analytics and attribution data
  • Include source/context information at creation
  • Update metadata incrementally as needed
  • Use consistent naming conventions (snake_case recommended)

Don’t:

  • Store sensitive data (passwords, tokens, PII)
  • Use deeply nested objects if you need to update nested fields (only top-level merge)
  • Store large binary data or files
  • Assume metadata is validated beyond JSON compatibility

Limitations

  • No Nested Merging: Only top-level keys are merged, nested objects are fully replaced
  • Size Limit: Total metadata JSON should be reasonable (< 10KB recommended)
  • No Schema Validation: Clients responsible for data consistency
  • No Indexing: Cannot efficiently filter sessions by arbitrary metadata keys
  • Type Preservation: All JSON types are preserved, but JavaScript-specific types (Date, RegExp) must be serialized to JSON-compatible formats (ISO strings, plain objects)

Next Steps