Skip to main content

Running a guided selling conversation

Not yet available

Guided selling is not yet available. This page describes it ahead of its release, so details can still change. The changelog announces when it is released.

Run a conversation step by step with the guidedSellingStep query, from the opening question to the advice. The examples use a label printer flow that advises one of four printers.

The query​

Every step uses the same query. Select both result types and use __typename to tell them apart:

query GuidedSellingStep($input: GuidedSellingStepInput!) {
guidedSellingStep(input: $input) {
__typename
... on GuidedSellingQuestion {
questionId
question
acknowledgement
options {
optionId
label
}
freeTextAllowed
}
... on GuidedSellingAdvice {
headline
items {
productId
reason
}
}
}
}

Starting a conversation​

Generate a new conversationId and send an empty list of answers:

{
"input": {
"flowId": "01a10f3c-5b7e-7d42-9a61-3c8e2f4b7d90",
"language": "EN",
"conversationId": "3f6c1a52-8e0b-4d7a-9c2e-5b8f0d4a7e19",
"answers": []
}
}

The response is the opening question:

{
"data": {
"guidedSellingStep": {
"__typename": "GuidedSellingQuestion",
"questionId": "1.DZSCCgULURhZHvfG",
"question": "Where will the printer be used?",
"acknowledgement": null,
"options": [
{ "optionId": "65y8-WXdEScJ5UCB", "label": "At a packing desk or in an office" },
{ "optionId": "jBtc1ESgEUu2iFBI", "label": "In a warehouse or on the production floor" },
{ "optionId": "pxWVsgmM7LzCzfwd", "label": "On the move: in the aisles or at customer sites" },
{ "optionId": "Rk2Wq9ZtXc4bN7aE", "label": "I already know which model I want" }
],
"freeTextAllowed": true
}
}
}

Show the question with its options as buttons, in the order given. When freeTextAllowed is true, also show a text field so the customer can answer in their own words. The label and placeholder of that field are yours to write.

If you leave out language, the flow answers in the language it was written in.

Sending a picked option​

When the customer picks an option, add the question id and the option id to the answers and send the whole list:

{
"input": {
"flowId": "01a10f3c-5b7e-7d42-9a61-3c8e2f4b7d90",
"language": "EN",
"conversationId": "3f6c1a52-8e0b-4d7a-9c2e-5b8f0d4a7e19",
"answers": [
{ "questionId": "1.DZSCCgULURhZHvfG", "optionId": "jBtc1ESgEUu2iFBI" }
]
}
}

The response is the next question:

{
"data": {
"guidedSellingStep": {
"__typename": "GuidedSellingQuestion",
"questionId": "1.Q7mR2xVb9KdLpE4s",
"question": "How many labels do you print on a busy day?",
"acknowledgement": "Got it: the printer will work in a warehouse or on the production floor.",
"options": [
{ "optionId": "Tq3vX8nWc1LmZ5yR", "label": "Up to 5,000" },
{ "optionId": "Hb6pK2sDf9GjN4wE", "label": "More than 5,000" },
{ "optionId": "Ue1oY7tAz3RcV8qM", "label": "Not sure" }
],
"freeTextAllowed": true
}
}
}

Show the acknowledgement with the new question. It is one short line that confirms the previous answer, and it is null on the opening question.

The last option is often an escape option such as "Not sure" or "Other / none of these". Treat it like any other option.

Sending a typed answer​

When the customer types instead, add the text with the id of the question that was on screen:

{
"input": {
"flowId": "01a10f3c-5b7e-7d42-9a61-3c8e2f4b7d90",
"language": "EN",
"conversationId": "3f6c1a52-8e0b-4d7a-9c2e-5b8f0d4a7e19",
"answers": [
{ "questionId": "1.DZSCCgULURhZHvfG", "optionId": "jBtc1ESgEUu2iFBI" },
{ "questionId": "1.Q7mR2xVb9KdLpE4s", "text": "About 8,000 a day, mostly 6-inch pallet labels" }
]
}
}

Here the answers identify the right printer, so the response is the advice:

{
"data": {
"guidedSellingStep": {
"__typename": "GuidedSellingAdvice",
"headline": "An industrial 6-inch printer for your daily pallet labels",
"items": [
{
"productId": 18241,
"reason": "It prints up to 10,000 labels a day on labels up to 6 inch wide, so it handles your 8,000 pallet labels a day."
}
]
}
}
}

A typed answer is read as the answer to the question it belongs to, so a short reply such as "yes" or "not sure" works. Everything else the customer writes is used too: this customer also gave the label width, so the flow did not ask for it.

The advice holds up to three products, best first. To turn it into product cards, see Showing the advice.

Keeping the conversation in your frontend​

Keep one ordered list of what the customer did and send it as answers. Picked and typed answers go in the same list, in the order they happened.

The customerYou send
Opens guided sellingA new conversationId and an empty list
Picks an optionThe list with { questionId, optionId } added
Types an answerThe list with { questionId, text } added, using the id of the question on screen
Goes back one stepThe list without its last answer
Starts overA new conversationId and an empty list
Switches the storefront languageA new conversationId and an empty list, in the new language

Going back returns the question the customer answered before. While the conversation has only picked answers, every step comes back exactly as before. Once it has a typed answer, its later steps are kept for 15 minutes and written again after that, so their wording can differ.

To show a summary of the answers on the advice screen, keep the option label next to each picked answer in your own state. Send only questionId with optionId or text.

Calling the step from your server​

Send the step from your server rather than from the browser. Every step that is not prepared in advance is a call to the flow's AI model, which the flow's owner pays for. A server route lets you:

  • keep your API key and the flow id out of the browser
  • send one fixed query, so the browser supplies only the language, the conversation id and the answers
  • add your own limits per visitor when you need them

Your route does not need to validate the answers. Guided selling checks every field and names each problem in the error (see Handling errors).

A server function that runs one step:

const ENDPOINT = 'https://api.helice.cloud/v2/graphql';

const STEP_QUERY = `
query GuidedSellingStep($input: GuidedSellingStepInput!) {
guidedSellingStep(input: $input) {
__typename
... on GuidedSellingQuestion {
questionId
question
acknowledgement
options { optionId label }
freeTextAllowed
}
... on GuidedSellingAdvice {
headline
items { productId reason }
}
}
}
`;

type Answer = { questionId: string; optionId: string } | { questionId: string; text: string };

export async function guidedSellingStep(
conversation: { language: string; conversationId: string; answers: Answer[] },
accessToken?: string,
) {
const response = await fetch(ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
apiKey: process.env.PROPELLER_API_KEY!,
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
},
body: JSON.stringify({
query: STEP_QUERY,
variables: {
input: {
flowId: process.env.GUIDED_SELLING_FLOW_ID,
language: conversation.language,
conversationId: conversation.conversationId,
answers: conversation.answers,
},
},
}),
// A step gives up after 45 seconds, so wait a little longer than that.
signal: AbortSignal.timeout(50_000),
});
const { data, errors } = await response.json();
if (errors?.length) {
throw Object.assign(new Error(errors[0].message), { extensions: errors[0].extensions });
}
return data.guidedSellingStep;
}

Pass the logged-in user's access token when there is one, the same as for your other GraphQL calls.

Handling the wait​

Most steps come back in milliseconds, but a step after a typed answer takes 5 to 20 seconds. These patterns keep both fast and slow steps comfortable:

  • Keep the current question on screen, dimmed and disabled, while the next step loads. Show a loading state only after about half a second, so fast steps do not flicker.
  • Say what is happening, for example "Finding the best next question". After a few seconds, add that a new answer can take a moment. A spinner without text gets abandoned.
  • Set your timeout above 45 seconds. The step returns an error when the model has not answered within 45 seconds.
  • Cancel the request in flight when the customer goes back or starts over.
  • Move focus to the new question and announce it to screen readers, for example with aria-live="polite".
  • Render every text from guided selling as plain text, never as HTML. Questions, options, acknowledgements, headlines and reasons are returned as plain text.

Handling errors​

Errors arrive as GraphQL errors with a code in extensions.code:

{
"errors": [
{
"message": "Language \"DE\" is not available for this guide.",
"path": ["guidedSellingStep"],
"extensions": {
"code": "GUIDED_SELLING_LANGUAGE_UNSUPPORTED_ERROR",
"supported": ["EN", "NL"]
}
}
],
"data": null
}

Retrying with the same input is always safe. Decide per code what the customer sees:

CodeWhat happenedWhat to do
GUIDED_SELLING_ANSWER_UNKNOWN_ERRORAn answer refers to a question or option the flow did not produce, or the conversation has a typed answer and is more than a day oldStart a new conversation, with a short note to the customer
GUIDED_SELLING_MODEL_UNAVAILABLE_ERRORThe AI model could not answer. extensions.retryable says whether trying again can help and extensions.reason says whyWhen retryable, offer to try again. Otherwise the flow's setup needs attention: hide guided selling and log the reason
GUIDED_SELLING_TURN_FAILED_ERRORThe model did not produce a valid step, also not on a second attemptOffer to try again or to start over
GUIDED_SELLING_QUOTA_EXCEEDED_ERRORThe environment's budget of model calls for this minute is used upAsk the customer to try again in a moment
GUIDED_SELLING_INPUT_INVALID_ERRORThe input breaks a rule, for example an answer with both optionId and text, or more answers than the flow's question budget. extensions.problems lists each problemFix your integration. The customer cannot
GUIDED_SELLING_LANGUAGE_UNSUPPORTED_ERRORThe flow does not offer this language. extensions.supported lists the languages it does offerStart the conversation in one of those languages
GUIDED_SELLING_FLOW_VERSION_NOT_ALLOWED_ERRORThe input names a version that only flow managers may runLeave version out
GUIDED_SELLING_FLOW_NOT_FOUND_ERROR, GUIDED_SELLING_FLOW_INACTIVE_ERROR, GUIDED_SELLING_FLOW_NOT_PUBLISHED_ERRORThe flow id is wrong, the flow is switched off or it has no published version yetHide guided selling and log it. The customer cannot fix this

Limits​

ItemLimit
Options per question2 to 6, an escape option included
Typed answerUp to 4,000 characters. Only the first 1,000 are read, so limit your text field to 1,000 characters.
Answers per conversationThe flow's question budget, at most 20. Every answer counts, picked or typed, the answer to the opening question included. When the budget is spent, the next step is the advice.
conversationId1 to 128 characters
QuestionUp to 300 characters
Option labelUp to 120 characters
AcknowledgementUp to 200 characters
HeadlineUp to 200 characters
ReasonUp to 400 characters
Advised products0 to 3

A generated text that would be longer than its limit is shortened at a word boundary and ends in an ellipsis, so your layout can rely on these lengths.

See also​