Trigger published flows from your backend, continue conversations over Socket.IO, embed chat on a website, or administer a workspace through scoped REST and MCP access.
zen_…Triggers one flowznwt_…Scoped workspace administrationExternal triggers use the production flow. Inactive flows reject new external conversations.
Open a flow → Integrate → Web & API, or use Settings → Developer → API tokens.
The zen_… token is a secret URL credential. Never embed it in browser source.
POST /api/trigger/:token creates a conversation or resumes an existing one.
export ZENYAP_BASE_URL="https://YOUR_ZENYAP_HOST"
export ZENYAP_API_TOKEN="zen_…"
curl --request POST "$ZENYAP_BASE_URL/api/trigger/$ZENYAP_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order-123-created" \
--data '{
"customerName": "Jane Doe",
"orderId": "123-ABC",
"userInput": "Where is my order?"
}'const response = await fetch(
`${process.env.ZENYAP_BASE_URL}/api/trigger/${process.env.ZENYAP_API_TOKEN}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
customerName: "Jane Doe",
orderId: "123-ABC",
userInput: "Where is my order?",
}),
},
);
if (!response.ok) {
throw new Error(`ZenYap returned ${response.status}: ${await response.text()}`);
}
const { sessionId, sessionToken } = await response.json();Send any JSON object. ZenYap keeps your custom fields under webhookData, making them available to flow nodes without allowing external data to overwrite system context.
| Field | Type | Behavior |
|---|---|---|
sessionId | string? | Reuses an active, unexpired conversation created by the same flow token. |
userInput | string? | Supplies the initial visitor turn, or resumes a flow currently waiting for input. |
clientMessageId | string? | Stable identity for one user turn. Send it with an HTTP continuation so retries cannot duplicate the transcript. |
| Any other field | JSON | Stored under webhookData for use by the flow. |
{
"success": true,
"message": "Session created/updated successfully. Connect via WebSocket and emit 'start_flow' to begin.",
"sessionId": "cm…",
"continuation": { "status": "created" },
"sessionToken": "eyJ…"
}Use the credentials for different jobs. The zen_… token remains on your trusted backend. The returned sessionToken is revocable and scoped to one conversation, so it is the only credential you pass to that conversation’s browser client.
For webhook-style flows, send the previous sessionId, the new userInput, and a stable clientMessageId. ZenYap first persists that exact user turn, then resumes the waiting node and returns continuation.status = resumed. It never replaces an unavailable session: missing, ended, expired, or wrong-flow IDs return 404 SESSION_NOT_AVAILABLE. A turn arriving before an input listener is armed returns 409 SESSION_NOT_WAITING with Retry-After.
await fetch(`${baseUrl}/api/trigger/${token}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
sessionId,
userInput: "It is order 123-ABC",
clientMessageId: crypto.randomUUID(),
}),
});Connect Socket.IO only after the trigger request returns a conversation-scoped sessionToken.
import { io } from "socket.io-client";
const socket = io(baseUrl, {
path: "/api/socket",
auth: { token: sessionToken },
});
let flowStarted = false;
socket.on("connect", () => {
socket.emit("join_session", sessionId, ({ ok }) => {
if (!ok) throw new Error("ZenYap session join was rejected");
if (!flowStarted) {
flowStarted = true;
socket.emit("start_flow", { sessionId });
}
});
});
socket.on("session_message_history", ({ messages }) => renderHistory(messages));
socket.on("bot_message", (message) => renderMessage(message));
socket.on("agent_message", (message) => renderMessage(message));
socket.on("bot_typing_start", () => setTyping(true));
socket.on("bot_typing_end", () => setTyping(false));
socket.on("flow_execution_state", ({ status }) => setTyping(status === "running"));
socket.on("session_ended", () => disableComposer());
export function sendMessage(content) {
socket.emit("user_message", {
sessionId,
content,
clientMessageId: crypto.randomUUID(),
});
}Do not emit start_flow before join_session acknowledges { ok: true }, and guard it so reconnects do not start the flow twice. When an HTTP request already resumed a waiting conversation with sessionId and userInput, rejoin for events but do not emit start_flow again.
| Event | Direction | Purpose |
|---|---|---|
join_session | Client → server | Authorizes and joins the conversation room; accepts an acknowledgement callback. |
start_flow | Client → server | Starts an active flow after a successful join. |
user_message | Client → server | Sends { sessionId, content, clientMessageId }. |
session_message_history | Server → client | Replays the conversation after joining. |
bot_message / agent_message | Server → client | Delivers automated or human replies. |
bot_typing_start / bot_typing_end | Server → client | Controls the typing state. |
flow_execution_state | Server → client | Reconciles running, waiting, completed, and failed even if a typing event was missed. |
session_ended | Server → client | Marks the composer read-only and ends reconnect attempts. |
For standard website chat, use the hosted widget instead of rebuilding the Socket.IO client.
<script
src="https://YOUR_ZENYAP_HOST/api/embed/zen_…/script?theme=auto&color=%23d64b78"
async
></script>Generate the exact snippet from Flow delivery → Web & API → Configure website widget. The setup supports HTML and React, theme selection, accent color, and a direct preview. The widget token still appears in the script URL, so use a dedicated revocable token and never reuse a higher-authority credential.
| Status | Meaning | Recovery |
|---|---|---|
202 | Accepted | Persist sessionId; connect with the returned sessionToken when realtime is needed. |
400 | Malformed JSON or reserved field | Fix the request body; do not retry unchanged. |
402 | Workspace or conversation limit reached | Surface the limit and resetsAt; do not retry immediately. |
403 | Invalid, revoked, or wrong token | Stop and replace the credential. |
404 | Requested continuation unavailable | Do not create or remap a replacement session. Keep the existing transcript and fall back explicitly. |
409 | Flow inactive, turn not ready, identity conflict, or identical request in flight | Honor Retry-After when present. Otherwise fix the state or identity; never assume the turn was accepted. |
429 | Rate limited | Honor Retry-After and back off. |
5xx | Temporary server failure | Retry with the same idempotency key and bounded exponential backoff. |
Send Idempotency-Key for every trigger. Keep it stable when retrying the same originating event. A completed duplicate replays the original response with X-Znowflow-Idempotent-Replay: true; an identical in-flight request returns 409 with Retry-After: 5.
Trigger requests are limited per flow token and client IP. A limited response returns 429 with Retry-After: 60.
Use a scoped znwt_… agent token with the REST base /api/admin/v1.
curl "$ZENYAP_BASE_URL/api/admin/v1/whoami" \
--header "Authorization: Bearer $ZENYAP_AGENT_TOKEN"Send Authorization: Bearer <agent-token>. The token fixes the workspace boundary; never send an organizationId. List routes use cursor pagination with limit up to 200.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /whoami | workspace:read | Verify the workspace, token, scopes, and expiry. |
GET | /projects | workspace:read | List projects. |
POST | /projects | projects:write | Create a project. |
PATCH / DELETE | /projects/{projectId} | projects:write | Update or delete a project. |
GET | /flows | workspace:read | List flows, optionally filtered by project. |
POST | /flows | flows:draft | Create an inactive unpublished draft. |
POST | /flows/{flowId}/publish | flows:publish | Publish a tested draft version. |
PUT | /flows/{flowId}/activation | flows:publish | Activate or deactivate a published flow. |
GET / POST | /intents | intents:read / intents:write | List and create intents; batch creation supports up to 50. |
PATCH / DELETE | /intents/{intentId} | intents:write | Update or delete an intent. |
GET / POST | /kb/articles | kb:read / kb:write | List and create knowledge-base articles. |
PATCH / DELETE | /kb/articles/{articleId} | kb:write | Update or delete a knowledge-base article. |
GET | /kb/search?q=… | kb:read | Search runtime-equivalent knowledge retrieval. |
POST | /assistant/messages | assistant:prompt | Prompt the workspace assistant; consumes AI budget. |
POST | /assistant/flow-drafts | assistant:prompt + flows:draft | Generate a graph and preflight report without persisting it. |
GET / POST | /tokens | tokens:read / tokens:write | List or mint workspace agent tokens. |
DELETE | /tokens/{tokenId} | tokens:write | Revoke a workspace agent token. |
GET | /audit | workspace:read | Read the rolling 30-day API audit trail. |
Least privilege is enforced. Write scopes require a writable workspace plan. Mutating calls are idempotent. The API is rate-limited to 30 requests per minute per token and IP, and every request records redacted audit metadata.
The same workspace agent token can authenticate the stateless MCP endpoint. Tools enforce their own scopes and expose projects, flows, publication, intents, knowledge, assistant prompting, tokens, and audit activity.
{
"mcpServers": {
"zenyap": {
"type": "http",
"url": "https://YOUR_ZENYAP_HOST/api/admin/v1/mcp",
"headers": {
"Authorization": "Bearer ${ZENYAP_AGENT_TOKEN}"
}
}
}
}This copy-ready SKILL.md includes the trigger contract, secret-handling rules, realtime event order, retries, and acceptance tests. In a flow’s Web & API tab it also includes the exact workspace, project, flow, origin, and—after explicit reveal—the selected token.
zen_… flow tokens and znwt_… agent tokens in server-side secret storage.sessionToken for browser Socket.IO authentication.