Back to Arky

Support

Support agents, website chat, conversations, assignment, and resolution

The Support module powers website chat and the Admin support inbox. Storefront visitors start conversations through the storefront support API. Admin users create support agents, reply as staff, assign conversations, and resolve them.

Storefront Chat

POST /v1/storefront/support/conversations
SDK: arky.support.startConversation()
POST /v1/storefront/support/conversations/{conversationId}/messages
SDK: arky.support.sendMessage()
GET /v1/storefront/support/conversations/{conversationId}
SDK: arky.support.getConversation()
import { initialize } from "arky-sdk/storefront";
import {
	clearDurableRequest,
	getOrCreateDurableRequest,
	withDurableRequestLock,
} from "arky-sdk/utils";

const arky = initialize("arky_pk_...");

const started = await arky.support.startConversation({
	agent_key: "default",
	channel_metadata: { page: window.location.pathname },
});

const credentials = {
	conversation_id: started.conversation.id,
	support_token: started.support_token,
};
const credentialsKey = `arky:support-conversation:${credentials.conversation_id}`;
const serializedCredentials = JSON.stringify(credentials);
localStorage.setItem(credentialsKey, serializedCredentials);
if (localStorage.getItem(credentialsKey) !== serializedCredentials) {
	throw new Error("Support conversation credentials could not be saved");
}

const request = {
	conversation_id: credentials.conversation_id,
	input: { type: "text", content: "I need help with an order." },
} as const;
const storageKey = `arky:support-message:${credentials.conversation_id}`;

const next = await withDurableRequestLock(
	storageKey,
	"support message",
	async () => {
		const durable = getOrCreateDurableRequest(
			storageKey,
			request,
			"support message",
		);
		const response = await arky.support.sendMessage({
			conversation_id: request.conversation_id,
			support_token: credentials.support_token,
			message_id: durable.id,
			input: request.input,
		});
		const recorded = response.messages.some(
			(message) =>
				message.id === durable.id &&
				message.conversation_id === request.conversation_id &&
				message.role === "user" &&
				message.content === request.input.content,
		);
		if (!recorded)
			throw new Error("Support response did not confirm the requested message");
		clearDurableRequest(durable, "support message");
		return response;
	},
);

const current = await arky.support.getConversation(credentials);

startConversation() returns the raw 64-character lowercase hexadecimal support_token once. Persist it durably with conversation_id; if that write cannot be verified, stop instead of continuing with a volatile fallback. Follow-up sendMessage() and getConversation() calls require the pair. The SDK removes support_token from the request payload and sends it only as X-Arky-Support-Token. Treat it as a bearer credential: never put it in logs, metadata, URLs, query parameters, or request bodies.

message_id is required for every visitor message. Persist one message ID with the exact message request, reuse it only when retrying that same request, and clear it only after the returned conversation contains the matching user message. The utility helpers above fail closed when durable browser storage or cross-tab locking is unavailable.

Parameters

Name Type Description
agent_key optional string Support agent key. Omit it to use the default active agent.
channel_metadata optional Record<string, unknown> Storefront context saved with the conversation.

Support Agents

POST /v1/stores/{storeId}/support/agents
SDK: sdk.automation.support.createAgent()
GET /v1/stores/{storeId}/support/agents
SDK: sdk.automation.support.findAgents()
PUT /v1/stores/{storeId}/support/agents/{id}
SDK: sdk.automation.support.updateAgent()
DELETE /v1/stores/{storeId}/support/agents/{id}
SDK: sdk.automation.support.deleteAgent()
const agent = await sdk.automation.support.createAgent({
	store_id: "store_abc123",
	key: "default",
	name: "Website Support",
	status: "active",
	entry_node_id: "hello",
	nodes: {
		hello: {
			type: "message",
			text: "Hi. How can we help?",
			buttons: ["Talk to us"],
		},
	},
	edges: [],
});

Support agents are store-scoped flow definitions. Nodes can send messages, collect input, hand off to AI mode, or hand off to human staff.

Admin Conversations

GET /v1/stores/{storeId}/support/conversations
SDK: sdk.automation.support.findConversations()
GET /v1/stores/{storeId}/support/conversations/{conversationId}
SDK: sdk.automation.support.getConversation()
POST /v1/stores/{storeId}/support/conversations/{conversationId}/reply
SDK: sdk.automation.support.replyToConversation()
POST /v1/stores/{storeId}/support/conversations/{conversationId}/assign
SDK: sdk.automation.support.assignConversation()
POST /v1/stores/{storeId}/support/conversations/{conversationId}/resolve
SDK: sdk.automation.support.resolveConversation()
import {
	clearDurableRequest,
	getOrCreateDurableRequest,
	withDurableRequestLock,
} from "arky-sdk/utils";

const inbox = await sdk.automation.support.findConversations({
	store_id: "store_abc123",
	status: "escalated",
	limit: 25,
});

await sdk.automation.support.assignConversation({
	store_id: "store_abc123",
	conversation_id: inbox.items[0].id,
	account_id: "acct_staff_1",
});

const replyRequest = {
	store_id: "store_abc123",
	conversation_id: inbox.items[0].id,
	content: "I can help with that.",
	resolve: false,
};
const replyStorageKey = `arky:support-staff-reply:${replyRequest.store_id}:${replyRequest.conversation_id}`;

await withDurableRequestLock(
	replyStorageKey,
	"support staff reply",
	async () => {
		const durable = getOrCreateDurableRequest(
			replyStorageKey,
			replyRequest,
			"support staff reply",
		);
		const response = await sdk.automation.support.replyToConversation({
			...replyRequest,
			message_id: durable.id,
		});
		const recorded = response.messages.some(
			(message) =>
				message.id === durable.id &&
				message.store_id === replyRequest.store_id &&
				message.conversation_id === replyRequest.conversation_id &&
				message.role === "staff" &&
				message.content === replyRequest.content,
		);
		if (!recorded)
			throw new Error(
				"Support response did not confirm the requested staff reply",
			);
		clearDurableRequest(durable, "support staff reply");
		return response;
	},
);

await sdk.automation.support.resolveConversation({
	store_id: "store_abc123",
	conversation_id: inbox.items[0].id,
});

Resolved conversations are closed. New visitor messages create or continue active conversations instead of reopening closed support state.

Staff replies also require a durable message_id. A retry of the exact same reply reuses that ID; a different reply uses a different message ID.