ZenYapDocs
Developer settingsOpen app
API referenceStart hereTrigger a flowRealtime chatWebsite widgetErrors & reliabilityWorkspace APIMCP & coding agentsSecurity

Build with ZenYap

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.

Send your first request Create a token
Flow API tokenzen_…Triggers one flow
Agent tokenznwt_…Scoped workspace administration

Before you call the API

  1. 1
    Publish and activate the flow

    External triggers use the production flow. Inactive flows reject new external conversations.

  2. 2
    Create or select a flow API token

    Open a flow → Integrate → Web & API, or use Settings → Developer → API tokens.

  3. 3
    Call ZenYap from your backend

    The zen_… token is a secret URL credential. Never embed it in browser source.

Trigger a flow

POST /api/trigger/:token creates a conversation or resumes an existing one.

cURL
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?"
  }'
JavaScript · server only
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();

Request body

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.

FieldTypeBehavior
sessionIdstring?Reuses an active, unexpired conversation created by the same flow token.
userInputstring?Supplies the initial visitor turn, or resumes a flow currently waiting for input.
clientMessageIdstring?Stable identity for one user turn. Send it with an HTTP continuation so retries cannot duplicate the transcript.
Any other fieldJSONStored under webhookData for use by the flow.

Success response

202 Accepted
{
  "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.

Continue without a socket

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.

Continue a conversation
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(),
  }),
});

Realtime chat

Connect Socket.IO only after the trigger request returns a conversation-scoped sessionToken.

Socket.IO client
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(),
  });
}

Connection order matters

TriggerConnectJoin + await ackStart flow

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.

EventDirectionPurpose
join_sessionClient → serverAuthorizes and joins the conversation room; accepts an acknowledgement callback.
start_flowClient → serverStarts an active flow after a successful join.
user_messageClient → serverSends { sessionId, content, clientMessageId }.
session_message_historyServer → clientReplays the conversation after joining.
bot_message / agent_messageServer → clientDelivers automated or human replies.
bot_typing_start / bot_typing_endServer → clientControls the typing state.
flow_execution_stateServer → clientReconciles running, waiting, completed, and failed even if a typing event was missed.
session_endedServer → clientMarks the composer read-only and ends reconnect attempts.

Website widget

For standard website chat, use the hosted widget instead of rebuilding the Socket.IO client.

HTML
<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.

Errors, limits, and safe retries

StatusMeaningRecovery
202AcceptedPersist sessionId; connect with the returned sessionToken when realtime is needed.
400Malformed JSON or reserved fieldFix the request body; do not retry unchanged.
402Workspace or conversation limit reachedSurface the limit and resetsAt; do not retry immediately.
403Invalid, revoked, or wrong tokenStop and replace the credential.
404Requested continuation unavailableDo not create or remap a replacement session. Keep the existing transcript and fall back explicitly.
409Flow inactive, turn not ready, identity conflict, or identical request in flightHonor Retry-After when present. Otherwise fix the state or identity; never assume the turn was accepted.
429Rate limitedHonor Retry-After and back off.
5xxTemporary server failureRetry with the same idempotency key and bounded exponential backoff.

Idempotency

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.

Workspace administration API

Use a scoped znwt_… agent token with the REST base /api/admin/v1.

Verify access first
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.

MethodPathScopePurpose
GET/whoamiworkspace:readVerify the workspace, token, scopes, and expiry.
GET/projectsworkspace:readList projects.
POST/projectsprojects:writeCreate a project.
PATCH / DELETE/projects/{projectId}projects:writeUpdate or delete a project.
GET/flowsworkspace:readList flows, optionally filtered by project.
POST/flowsflows:draftCreate an inactive unpublished draft.
POST/flows/{flowId}/publishflows:publishPublish a tested draft version.
PUT/flows/{flowId}/activationflows:publishActivate or deactivate a published flow.
GET / POST/intentsintents:read / intents:writeList and create intents; batch creation supports up to 50.
PATCH / DELETE/intents/{intentId}intents:writeUpdate or delete an intent.
GET / POST/kb/articleskb:read / kb:writeList and create knowledge-base articles.
PATCH / DELETE/kb/articles/{articleId}kb:writeUpdate or delete a knowledge-base article.
GET/kb/search?q=…kb:readSearch runtime-equivalent knowledge retrieval.
POST/assistant/messagesassistant:promptPrompt the workspace assistant; consumes AI budget.
POST/assistant/flow-draftsassistant:prompt + flows:draftGenerate a graph and preflight report without persisting it.
GET / POST/tokenstokens:read / tokens:writeList or mint workspace agent tokens.
DELETE/tokens/{tokenId}tokens:writeRevoke a workspace agent token.
GET/auditworkspace:readRead 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.

MCP and coding agents

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.

MCP configuration
{
  "mcpServers": {
    "zenyap": {
      "type": "http",
      "url": "https://YOUR_ZENYAP_HOST/api/admin/v1/mcp",
      "headers": {
        "Authorization": "Bearer ${ZENYAP_AGENT_TOKEN}"
      }
    }
  }
}

Give the runtime integration to a coding agent

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.

Security checklist

  • Keep zen_… flow tokens and znwt_… agent tokens in server-side secret storage.
  • Use the conversation-scoped sessionToken for browser Socket.IO authentication.
  • Use separate tokens per environment and integration so revocation has a small blast radius.
  • Never log credentials, authorization headers, raw request bodies containing personal data, or token-bearing URLs.
  • Revoke credentials immediately from Settings → Developer when exposure is suspected.