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 |
|---|---|---|
|
String |
Discriminator of the specification type — one of |
|
String |
Stable identifier of the specification within the project. Used as the |
|
String | null |
Ref of the parent |
|
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. |
|
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 |
|
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. |
|
String | null |
The locked state of the entry. |
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 |
|---|---|---|
|
none |
Grouping node. Holds no target of its own; its children reference it through |
|
|
Starts a new session with the agent |
|
|
Reference to one of the project’s configured actions, not literal wording — unlike |
|
|
In-app route to navigate to. Currently only agent routes are used: |
|
|
External URL to open, typically in a browser or an in-app web view — e.g. the customer’s CRM,
|
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
textandactionentries open a new session.Plan my daynames its agent inagentRefand sends itstextverbatim; the others leaveagentRefnulland go to the featured agent.Daily check-insends only the referencedailyCheckIn, 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 itnull. Both are conditioned on evaluations of the user’s conversations —Negotiation traineron an insight recording a completed coaching session,Price objection roleplayon 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.
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"
}
]
}
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:
-
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. -
For an unlock only, sends a push notification to each of the user’s registered devices. A revealed entry is not notified.
-
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:
-
refis 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. -
refis unknown — insert the delivered entry into the tree, at the end of the entries under itsparentRef. 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 |
|---|---|---|
|
String |
Ref of the navigation specification, as published in the project’s |
Query Parameters
| Parameter | Required | Type | Description |
|---|---|---|---|
|
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.