Integration

Navigation API

A project can publish navigation specifications — entry points in the host application that an agent may direct the user to. They are delivered to clients as the navigationSpecifications array of the project payload returned by GET /api/v2/project/{idOrRef} and GET /api/v2/project?agentKey={agentKey} (see Project and Agent API), and the endpoint on this page resolves whether an individual entry point is currently active for a user.

A specification can also reach the client mid-session, when an evaluation unlocks it — see Runtime Updates.

Navigation Specification Structure

The project payload carries a flat array of specifications. Nesting is expressed through parentRef, not through embedded arrays — a client builds the tree by grouping entries on their parentRef. The array is already in display order, so keep the order in which the entries of each level arrive.

Only specifications that are static and active in the project configuration, and not hidden for the user (see Entry State), are published this way. Both flags are server-side gates and neither is part of the payload:

  • isActive — an entry deactivated in the project configuration is dropped from the array entirely. A client therefore never receives an inactive entry and never has to filter on one.

  • isStatic — a non-static entry is not part of the navigation the client renders up front. It reaches the client only at runtime, when an evaluation unlocks it (see Runtime Updates).

Common Fields

Every specification, regardless of its type, carries these fields.

Field Type Description

type

String

Discriminator of the specification type — one of folder, text, action, route, url. See Specification Types.

ref

String

Stable identifier of the specification within the project. Used as the {ref} path parameter of GET /api/navigation/status/{ref} and as the parentRef of its children.

parentRef

String | null

Ref of the parent folder, or null for a top-level entry.

image

String | null

Absolute URL of a background image for the entry, when one is configured. Render blurred and darkened except top right corner to make the rest information in the tile readable.

name

String

Human-readable label of the entry, to render in the navigation UI and to use as the title of a push notification announcing it. Tool-backed types are exposed to the agent under their ref, not under this name.

description

String

Longer description of the entry. May be rendered as a subtitle and is the text the agent reasons over when deciding whether to navigate the user here.

disabledMessage

String | null

The locked state of the entry. null means the entry is unlocked and can be used. A string means the entry is locked, and the string is the message to show when the user picks it. It is always populated for a locked entry: a specification may carry its own wording, and one that does not falls back to the project’s defaultDisabledMessage, itself defaulting to Currently not available. There is no separate boolean — disabledMessage != null is the whole test.

Entry State

Each entry is in one of three states for a given user:

  • unlocked — delivered with disabledMessage: null, and can be used;

  • locked — delivered with a disabledMessage;

  • hidden — not delivered at all. A hidden folder takes its whole subtree with it, so a client never receives an entry whose ancestor folder is hidden.

Every entry starts in a default state configured in the project. Without one, an entry with a reveal condition starts hidden, an entry with an unlock condition starts locked, and any other entry starts unlocked. Two things can change the state for a user, and the engine records the change for that user and project:

  • an evaluation moves the entry up, never down. An entry can carry two conditions on the results of evaluations: a reveal condition, which shows a hidden entry as locked, and an unlock condition, which unlocks a locked entry. When one evaluation satisfies both, a hidden entry goes straight to unlocked. An entry without a reveal condition is never revealed by an evaluation, and an evaluation never locks or hides an entry;

  • an agent the project allows to manage the entry can change its state — for example a coach revealing the roleplay partners it recommends to the user, and hiding them again later. The change is decided at the end of the session, when the engine reviews the whole conversation against the agent’s instructions.

An entry belongs to one of the two, never to both: an entry with a reveal or an unlock condition is changed only by evaluations, even when the project also allows an agent to manage it, so access the user has earned is never taken away by the agent and access the user has to earn is never granted by it.

Conditions also count evaluations the user had before the entry was added or its conditions were changed. Whenever the user’s navigation is read, the engine checks every entry an evaluation could still move up against all of the user’s earlier evaluations in the project and moves it up when they satisfy a condition — so an entry unlocked by a conversation the user already had is delivered unlocked from the start. This catch-up is silent: it sends no push notification and no pipeline element, and the entry simply arrives in its new state in the next project payload or status response.

An unauthenticated request receives every entry in its default state.

A locked entry is still delivered in the payload — render it as locked and surface disabledMessage, rather than hiding it. Hiding is decided by the engine.

Specification Types

Each type adds its own payload field on top of the common ones.

type Extra field Behaviour

folder

none

Grouping node. Holds no target of its own; its children reference it through parentRef. Folders may nest — a folder can itself have a parentRef.

text

text (String), agentRef (String | null)

Starts a new session with the agent agentRef, or with the project’s featured agent (featuredAgentRef) when agentRef is null, sending this text as the input — as if the user had said it, e.g. Tell me about my account state. The conversation then continues from the agent’s answer.

action

actionName (String), agentRef (String | null)

Reference to one of the project’s configured actions, not literal wording — unlike text, the payload carries no message of its own. Starts a new session with the agent agentRef, or with the featured agent when agentRef is null, by sending #{actionName} as the input, the same action-trigger mechanism #intro uses to open a session (see Wire Formats). The engine looks the ref up among the project’s configured actions and sends the text configured for the user’s locale, as if they had said it; a name with no match instead just tells the agent the user performed that action.

route

route (String)

In-app route to navigate to. Currently only agent routes are used: /a/{agentRef} opens a conversation with that agent — e.g. /a/salesAssistant.

url

url (String)

External URL to open, typically in a browser or an in-app web view — e.g. the customer’s CRM, https://some.crm.com. The URL is the same for every user of the project.

Reference Example

The example below is the navigation of a sales team’s app and covers every supported type across three top-level folders. Sales is the only one with subfolders — My day and Customers. The types are spread over the tree rather than repeated in every folder, which is how a real project is usually laid out. Navigation is configured per project, so every entry is the same for all its users; only its state is resolved per user.

  • The text and action entries open a new session. Plan my day names its agent in agentRef and sends its text verbatim; the others leave agentRef null and go to the featured agent. Daily check-in sends only the reference dailyCheckIn, which the engine resolves to the wording the project configured for the user’s locale.

  • Two entries are locked, and are exactly the two carrying a disabledMessage; the rest leave it null. Both are conditioned on evaluations of the user’s conversations — Negotiation trainer on an insight recording a completed coaching session, Price objection roleplay on a coaching score of at least 3. The conditions themselves are server-side and not part of the payload.

Four entries — one per non-folder type — point image at a real, publicly reachable photograph, shown as a thumbnail beside their title in the diagram; the folders use placeholder URLs on a fictional CDN.

Tree of the navigation specifications in the reference example

And the payload that produces it:

{
  "navigationSpecifications": [
    {
      "type": "folder",
      "ref": "sales",
      "parentRef": null,
      "image": "https://cdn.example.com/acme/nav/sales.png",
      "name": "Sales",
      "description": "Day-to-day sales work.",
      "disabledMessage": null
    },
    {
      "type": "folder",
      "ref": "myDay",
      "parentRef": "sales",
      "image": null,
      "name": "My day",
      "description": "Planning and reviewing the working day.",
      "disabledMessage": null
    },
    {
      "type": "text",
      "ref": "planDay",
      "parentRef": "myDay",
      "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0f/Analytics_2016-02-26_at_10.19.24_AM.jpg/960px-Analytics_2016-02-26_at_10.19.24_AM.jpg",
      "name": "Plan my day",
      "description": "Starts a session in which the agent helps plan the day.",
      "disabledMessage": null,
      "agentRef": "salesAssistant",
      "text": "Help me plan my day"
    },
    {
      "type": "action",
      "ref": "dailyCheckIn",
      "parentRef": "myDay",
      "image": null,
      "name": "Daily check-in",
      "description": "Starts the daily check-in with the agent, in the user's language.",
      "disabledMessage": null,
      "agentRef": null,
      "actionName": "dailyCheckIn"
    },
    {
      "type": "folder",
      "ref": "customers",
      "parentRef": "sales",
      "image": null,
      "name": "Customers",
      "description": "Customer meetings and records.",
      "disabledMessage": null
    },
    {
      "type": "text",
      "ref": "meetingPrep",
      "parentRef": "customers",
      "image": null,
      "name": "Prepare a meeting",
      "description": "Starts a session that prepares the next customer meeting.",
      "disabledMessage": null,
      "agentRef": null,
      "text": "Help me prepare for my next customer meeting"
    },
    {
      "type": "url",
      "ref": "crm",
      "parentRef": "customers",
      "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/Handshake-2056021.jpg/960px-Handshake-2056021.jpg",
      "name": "Open CRM",
      "description": "Opens the customer records in the external CRM.",
      "disabledMessage": null,
      "url": "https://some.crm.com"
    },
    {
      "type": "route",
      "ref": "salesCoach",
      "parentRef": "sales",
      "image": null,
      "name": "Sales coach",
      "description": "Starts a conversation with the sales coach.",
      "disabledMessage": null,
      "route": "/a/salesCoach"
    },
    {
      "type": "folder",
      "ref": "assistants",
      "parentRef": null,
      "image": "https://cdn.example.com/acme/nav/assistants.png",
      "name": "Assistants",
      "description": "Agents the user can switch to.",
      "disabledMessage": null
    },
    {
      "type": "route",
      "ref": "salesAssistant",
      "parentRef": "assistants",
      "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Business-170645.jpg/960px-Business-170645.jpg",
      "name": "Sales assistant",
      "description": "Starts a conversation with the sales assistant.",
      "disabledMessage": null,
      "route": "/a/salesAssistant"
    },
    {
      "type": "route",
      "ref": "negotiationTrainer",
      "parentRef": "assistants",
      "image": null,
      "name": "Negotiation trainer",
      "description": "Starts a conversation with the negotiation trainer.",
      "disabledMessage": "Unlocks after your first coaching session.",
      "route": "/a/negotiationTrainer"
    },
    {
      "type": "folder",
      "ref": "training",
      "parentRef": null,
      "image": "https://cdn.example.com/acme/nav/training.png",
      "name": "Training",
      "description": "Practice for customer conversations.",
      "disabledMessage": null
    },
    {
      "type": "action",
      "ref": "priceObjection",
      "parentRef": "training",
      "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Laptop_with_many_charts_on_screen_on_cluttered_desk.jpg/960px-Laptop_with_many_charts_on_screen_on_cluttered_desk.jpg",
      "name": "Price objection roleplay",
      "description": "Starts a roleplay of handling a price objection, in the user's language.",
      "disabledMessage": "Unlocks once your coaching score reaches 3.",
      "agentRef": null,
      "actionName": "priceObjection"
    },
    {
      "type": "url",
      "ref": "playbook",
      "parentRef": "training",
      "image": null,
      "name": "Sales playbook",
      "description": "Opens the sales playbook on the company intranet.",
      "disabledMessage": null,
      "url": "https://intranet.example.com/sales-playbook"
    }
  ]
}

Rendered

One top-level section of the same payload — the assistants folder — as a client would render it. Every string comes from the entries above; the numbers key each part of a tile to the field it is built from.

The assistants section rendered as tiles

Runtime Updates

Navigation is not only fetched up front. When an evaluation finishes, the engine re-checks the project’s navigation specifications against that evaluation’s results and delivers the ones whose state it has just changed: an entry it unlocks over two channels at once, an entry it reveals over the pipeline only.

A specification is picked up when all of these hold:

  • it is active in the project configuration;

  • it has a reveal condition or an unlock condition satisfied by the insight or featured values the evaluation produced;

  • that condition applies to the entry’s current state for the user: a reveal condition to a hidden entry, an unlock condition to a locked one.

An entry with neither condition is never delivered this way — its state does not depend on evaluations. Neither is an entry moved up by the catch-up on earlier evaluations described in Entry State. An entry already past its condition is skipped, so a repeated match does not produce a second delivery, and a hidden entry without a reveal condition stays hidden.

For each specification it picks up, the engine does three things:

  1. Records the new state for that user and project, so GET /api/navigation/status/{ref} and every later project payload deliver the entry in it.

  2. For an unlock only, sends a push notification to each of the user’s registered devices. A revealed entry is not notified.

  3. Emits a pipeline output element into the running session.

An entry unlocked by an agent at the end of the session goes through these same three steps; see Unlocked by an Agent.

Applying an Update by Ref

Both channels carry the specification’s ref, and ref is the identity of an entry within a project. A client applies an update by matching on it:

  • ref is already known — replace the stored entry with the delivered one. This is how a static entry already rendered from the project payload flips from locked to unlocked.

  • ref is unknown — insert the delivered entry into the tree, at the end of the entries under its parentRef. This is how a non-static entry, which is never part of the project payload, first appears, and how a revealed entry, which was hidden until now, first appears.

A delivered specification arrives either unlocked, with disabledMessage: null, or — when an evaluation has just revealed it — locked, with its disabledMessage. Updates only move an entry up. There is no element for locking or hiding, and none for withdrawal: an entry that the agent locks or hides for the user, deactivated or deleted in the project configuration simply stops being delivered. Re-fetch the project payload to pick those changes up — the pushed stream alone never shrinks a client’s navigation tree.

Pipeline Element

The specification arrives on the conversation transport as a navigation-specification output element, carrying the same object as the project payload — see Pipeline Socket. turnId is always null, because the change belongs to the evaluation rather than to a turn.

An unlocked entry:

{
  "type": "navigation-specification",
  "sessionId": "9f1d2c3b-4a5e-6f70-8192-a3b4c5d6e7f8",
  "turnId": null,
  "time": 1774000000000,
  "specification": {
    "type": "route",
    "ref": "negotiationTrainer",
    "parentRef": "assistants",
    "image": null,
    "name": "Negotiation trainer",
    "description": "Starts a conversation with the negotiation trainer.",
    "disabledMessage": null,
    "route": "/a/negotiationTrainer"
  }
}

A revealed entry arrives locked. Had Price objection roleplay been hidden until a first coaching session, the evaluation of that session would deliver it as:

{
  "type": "navigation-specification",
  "sessionId": "9f1d2c3b-4a5e-6f70-8192-a3b4c5d6e7f8",
  "turnId": null,
  "time": 1774000000000,
  "specification": {
    "type": "action",
    "ref": "priceObjection",
    "parentRef": "training",
    "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Laptop_with_many_charts_on_screen_on_cluttered_desk.jpg/960px-Laptop_with_many_charts_on_screen_on_cluttered_desk.jpg",
    "name": "Price objection roleplay",
    "description": "Starts a roleplay of handling a price objection, in the user's language.",
    "disabledMessage": "Unlocks once your coaching score reaches 3.",
    "agentRef": null,
    "actionName": "priceObjection"
  }
}

Push Notification

The same unlock is sent to every FCM token registered for the user. A user with no registered token receives nothing — the unlock is still recorded, so the entry appears unlocked the next time the project payload is fetched.

The notification’s title is the specification’s name and its body is the description. The picture is the specification’s image, falling back to the thumbnail of the project’s published featured agent when the specification has none.

data is a flat, string-keyed subset of the specification, not the whole object. It always carries the specification’s ref and the projectRef of the project it belongs to. type and its payload field are present for text, action, route and url entries, and absent for a folder; text and action entries also carry agentRef when one is set. name, parentRef, description, image and disabledMessage are not included, and keys with no value are dropped. Treat it as enough to identify and act on the entry — take the full object from the pipeline element or the project payload.

The Negotiation trainer entry has no image, so its notification carries the featured agent’s thumbnail:

{
  "id": "4c6b8a90-1d2e-3f40-5a6b-7c8d9e0f1a2b",
  "title": "Negotiation trainer",
  "body": "Starts a conversation with the negotiation trainer.",
  "image": "https://cdn.example.com/acme/agents/acme-support.png",
  "data": {
    "ref": "negotiationTrainer",
    "projectRef": "acme",
    "type": "route",
    "route": "/a/negotiationTrainer"
  }
}

The Price objection roleplay entry is an action with no agentRef, so agentRef is dropped from its data; an entry that names its agent carries it as "agentRef": "salesAssistant". It has no image either, so the picture is again the featured agent’s thumbnail:

{
  "id": "7e2f4a1b-3c5d-4e6f-8a9b-0c1d2e3f4a5b",
  "title": "Price objection roleplay",
  "body": "Starts a roleplay of handling a price objection, in the user's language.",
  "image": "https://cdn.example.com/acme/agents/acme-support.png",
  "data": {
    "ref": "priceObjection",
    "projectRef": "acme",
    "type": "action",
    "actionName": "priceObjection"
  }
}

Unlocked by an Agent

An entry an agent manages (see Entry State) changes state only at the end of the session, before the session’s evaluations run. Nothing changes while the conversation is running. The engine reviews the whole conversation once, against the agent’s instructions, and sets each entry’s new state. An entry it unlocks is handled like an evaluation unlock: the change is recorded, a push notification is sent and a pipeline output element is emitted at the end of the session’s output. An entry it locks or hides is only recorded, and shows up in the next project payload. The review also runs when the client disconnects instead of ending the session; the changes are then recorded and unlocks pushed, but no pipeline element can be emitted. An evaluation of the same session unlocks an entry only while it is still locked, so it never reveals an entry the review has just hidden.

GET /api/navigation/status/{ref}

Resolve whether an in-app navigation hint is currently active for the authenticated user. Requires the consumer role.

Use it to re-check a single entry without refetching the whole project — for example after a conversation that may have unlocked it.

Path Parameters

Parameter Type Description

ref

String

Ref of the navigation specification, as published in the project’s navigationSpecifications.

Query Parameters

Parameter Required Type Description

projectRef

Yes

String

Project the navigation specification belongs to.

Response

{ "isEnabled": true }

This endpoint answers with a plain boolean rather than with a specification, so it is the one place the state is reported as isEnabled instead of through disabledMessage. isEnabled: true means the entry is unlocked for the user. isEnabled: false means it is locked or hidden: a locked entry is one with a non-null disabledMessage, and a hidden entry is missing from the project payload.

The engine resolves the state recorded for the user, falling back to the entry’s default state. Use it to re-check a single entry — after a conversation that may have unlocked it, say — without refetching the whole project.