API Reference

Voipai Interactions API

Push customer interactions into Voipai and read the AI intelligence back — analyses, recurring issues, and the recommendations they drive. Each workspace runs on its own server; point this page at yours below, then authenticate with your API key.

Configuration

Enter the base URL of your Voipai server (given to you at onboarding — e.g. https://s24.gooyatel.com/api/v1). Everything on this page — samples and live calls — targets it. Stored only in your browser.

Authentication

Every request carries a tenant API key as a bearer token. Issue and revoke keys in your panel under Settings → Integrations. Keep the key server-side; it is shown only once. This is a secret key (vpk_…) for server-to-server calls — never put it in a browser. For the in-browser website chatbot use the separate public widget key instead.

Authorization: Bearer <your-api-key>
GET/pingscope: read

Verify a key

A cheap health/auth check: confirms your key and base URL and echoes back the workspace it is bound to and the scopes it carries. Start here when wiring an integration.

curl -X GET "https://YOUR-SERVER/api/v1/ping" \
  -H "Authorization: Bearer $VOIPAI_KEY"
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "ok": true,
  "workspace": "Acme Support",
  "workspace_id": 42,
  "scopes": [
    "read",
    "write"
  ],
  "key_prefix": "vpk_abcd",
  "server_time": "2026-07-27T12:00:00+00:00"
}
POST/interactionsscope: write

Ingest an interaction

Push a ticket or chat into Voipai. It is queued for AI analysis, embedding, case-linking and clustering — the same pipeline calls flow through.

curl -X POST "https://YOUR-SERVER/api/v1/interactions" \
  -H "Authorization: Bearer $VOIPAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"channel":"ticket","customer":"[email protected]","kind":"email","started_at":"2026-07-20T10:00:00","subject":"Internet down","body":"My internet has been down since last night.","agent_reply":"We opened a ticket and are checking the line."}'
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "id": 4821,
  "status": "accepted",
  "channel": "ticket"
}
GET/interactionsscope: read

List recent interactions

A page of interactions, newest first. Keyset pagination: pass `before` (an id) and read `next_before` from the response to walk back through history.

Parameters

limitqueryoptionalMax results (default 50, max 200)
beforequeryoptionalReturn interactions with id < this
channelqueryoptionalFilter by channel (e.g. ticket)
curl -X GET "https://YOUR-SERVER/api/v1/interactions" \
  -H "Authorization: Bearer $VOIPAI_KEY"
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "interactions": [
    {
      "id": 4821,
      "channel": "ticket",
      "direction": "inbound",
      "started_at": "2026-07-20T10:00:00+00:00",
      "pipeline_status": "analyzed",
      "customer_id": 317,
      "external_ref": "ZD-9912"
    }
  ],
  "next_before": 4820
}
GET/interactions/{id}scope: read

Get an interaction & its analysis

Read one interaction with the AI’s structured read: intent, sentiment, QA, churn/escalation signals, resolution, topics and a summary.

Parameters

idpathrequiredInteraction id
curl -X GET "https://YOUR-SERVER/api/v1/interactions/1" \
  -H "Authorization: Bearer $VOIPAI_KEY"
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "id": 4821,
  "channel": "ticket",
  "direction": "inbound",
  "started_at": "2026-07-20T10:00:00+00:00",
  "pipeline_status": "analyzed",
  "customer_id": 317,
  "analysis": {
    "status": "ready",
    "intent": "internet outage",
    "sentiment_customer": -1,
    "qa_score": 6,
    "churn_signal": false,
    "escalation_signal": false,
    "resolution": "follow_up_promised",
    "topics": [
      "internet",
      "outage"
    ],
    "summary": "Customer reported an outage since last night; a line check was promised."
  }
}
GET/issuesscope: read

List active issues

The recurring themes behind your interactions, ranked by impact — the systemic problems worth fixing once.

Parameters

limitqueryoptionalMax results (default 50)
curl -X GET "https://YOUR-SERVER/api/v1/issues" \
  -H "Authorization: Bearer $VOIPAI_KEY"
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "issues": [
    {
      "id": 12,
      "label": "internet slowness",
      "size": 22,
      "impact": 43,
      "status": "active",
      "last_seen": "2026-07-27T14:06:20+00:00"
    }
  ]
}
GET/recommendationsscope: read

List recommendations

Pending, actionable recommendations generated from risk signals and issues — the decision queue, over the wire.

Parameters

limitqueryoptionalMax results (default 50)
curl -X GET "https://YOUR-SERVER/api/v1/recommendations" \
  -H "Authorization: Bearer $VOIPAI_KEY"
Try it

Set your server base URL and API key at the top to enable live calls.

Example response

{
  "recommendations": [
    {
      "id": 88,
      "kind": "risk_intervention",
      "priority": 2,
      "title": "Reach out to an at-risk customer",
      "body": "A recent interaction signalled churn. Follow up before the customer leaves.",
      "customer_id": 317,
      "created_at": "2026-07-27T12:00:00+00:00"
    }
  ]
}

Website Chatbot Widget

public key

Drop a chat widget on your own website that answers from your Voipai knowledge base — and, when you connect your site, from its live products and pages too. Unlike the Interactions API above, this uses a public key (wgk_…) that ships in your page’s JavaScript. It grants no access to your data — every call is gated server-side by the widget’s origin allowlist and rate limits. Calls run from the browser under CORS (no bearer token, no cookies), and answers can be streamed. Create a widget and set its allowed domains in your panel under چت‌بات.

On WordPress? Install Gooyatel WP Client instead of writing any of this. It renders the widget on every page, and gives Gooyatel a signed, read-only endpoint so answers can quote your real WooCommerce prices and stock. Nothing below is needed — the plugin does it.

Embed on your site

A minimal, dependency-free streaming example. Paste the key from your panel; the visitor’s messages stream a grounded answer, and the returned session_id keeps context across turns. Render links as clickable elements rather than pulling URLs out of the answer text — the answer is model output, the links are not.

<!-- گویاتل chat widget — minimal streaming example (drop into your page) -->
<div id="gt-chat-log" dir="rtl"></div>
<form id="gt-chat-form"><input id="gt-chat-input" autocomplete="off" /></form>
<script>
  const WIDGET_KEY = "wgk_your_public_key";          // PUBLIC — safe to ship in the page
  const BASE = "https://YOUR-SERVER/api/widget";
  let sessionId = null;

  async function ask(message) {
    const res = await fetch(BASE + "/chat/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ key: WIDGET_KEY, message, session_id: sessionId }),
    });
    const reader = res.body.getReader();
    const dec = new TextDecoder();
    let buf = "", bubble = null;
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      buf += dec.decode(value, { stream: true });
      const frames = buf.split("\n\n"); buf = frames.pop();
      for (const frame of frames) {
        const ev = JSON.parse(frame.replace(/^data: /, ""));
        if (ev.session_id) sessionId = ev.session_id;      // persist context
        if (ev.delta) { (bubble ??= addBubble("bot")).textContent += ev.delta; }
        if (ev.done) renderLinks(ev.links);                // products / pages to click
        if (ev.error) addBubble("bot").textContent = ev.error;
      }
    }
  }

  function addBubble(who) {
    const el = document.createElement("div");
    el.className = "gt-" + who;
    document.getElementById("gt-chat-log").appendChild(el);
    return el;
  }
  // Links arrive as data, never inside the answer text — build them as elements
  // so a reply can never inject markup, and so the URL is always exactly right.
  function renderLinks(links) {
    for (const link of links || []) {
      const a = document.createElement("a");
      a.href = link.url;
      a.target = "_blank";
      a.rel = "noopener noreferrer";
      // link.was is the pre-sale price — strike it, never print it beside the
      // new one as a second number.
      a.textContent = link.was
        ? link.title + " — " + link.meta + " (was " + link.was + ")"
        : link.meta ? link.title + " — " + link.meta : link.title;
      document.getElementById("gt-chat-log").appendChild(a);
    }
  }

  document.getElementById("gt-chat-form").addEventListener("submit", (e) => {
    e.preventDefault();
    const input = document.getElementById("gt-chat-input");
    if (!input.value.trim()) return;
    addBubble("user").textContent = input.value;
    ask(input.value);
    input.value = "";
  });
</script>
POST/api/widget/chatpublic · CORS

Grounded answer (JSON)

One grounded answer over your knowledge base and, if a site connector is configured, your live site. Returns the answer text, any products or pages to link to, and a session id — send that id back on the next message to keep conversational context.

Request body

{
  "key": "wgk_your_public_key",
  "message": "ساعت کاری شما چیست؟",
  "session_id": null,
  "contact": "[email protected]"
}

Example (JavaScript)

const res = await fetch("https://YOUR-SERVER/api/widget/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    key: "wgk_your_public_key",
    message: "ساعت کاری شما؟",
    session_id: null,          // returned in the reply — send it back to keep context
    contact: "[email protected]" // optional: links the chat to a customer profile
  }),
});
const data = await res.json();
// { answer, links: [{ type, title, url, meta, was, image }], session_id }

Example response

{
  "answer": "ساعت کاری ما ۹ تا ۱۷ است.",
  "links": [
    {
      "type": "product",
      "title": "میز اداری",
      "url": "https://example.com/product/desk/",
      "meta": "۲٬۵۰۰٬۰۰۰ تومان",
      "was": "۲٬۹۰۰٬۰۰۰ تومان",
      "image": "https://example.com/wp-content/uploads/desk.jpg"
    }
  ],
  "session_id": "a1b2c3d4"
}
POST/api/widget/chat/streampublic · CORS

Grounded answer (streaming · SSE)

The same answer, streamed token-by-token as Server-Sent Events for a live typing effect. The first event carries the session id, then deltas, then a final done event with any links.

Request body

{
  "key": "wgk_your_public_key",
  "message": "ساعت کاری شما چیست؟",
  "session_id": "a1b2c3d4"
}

Consume the stream (JavaScript)

const res = await fetch("https://YOUR-SERVER/api/widget/chat/stream", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ key: "wgk_your_public_key", message: "ساعت کاری شما؟", session_id: null }),
});
const reader = res.body.getReader();
const dec = new TextDecoder();
let buf = "";
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += dec.decode(value, { stream: true });
  const frames = buf.split("\n\n"); buf = frames.pop();
  for (const frame of frames) {
    const ev = JSON.parse(frame.replace(/^data: /, ""));
    if (ev.session_id) console.log("session", ev.session_id);
    if (ev.delta) process.stdout.write(ev.delta);
    if (ev.done) console.log("\nlinks", ev.links);
  }
}

Event stream

data: {"session_id": "a1b2c3d4"}

data: {"delta": "ساعت کاری "}

data: {"delta": "ما ۹ تا ۱۷ است."}

data: {"done": true, "links": [{"type": "product", "title": "میز اداری", "url": "https://example.com/product/desk/", "meta": "۲٬۵۰۰٬۰۰۰ تومان", "was": "۲٬۹۰۰٬۰۰۰ تومان", "image": "https://example.com/…/desk.jpg"}]}
GET/api/widget/configpublic · CORS

Widget bootstrap

The widget’s public name and greeting — enough to render the launcher and opening bubble before the first message is sent.

Parameters

keyqueryrequiredThe widget public key (wgk_…)

Example (JavaScript)

const res = await fetch("https://YOUR-SERVER/api/widget/config?key=wgk_your_public_key");
const { name, greeting } = await res.json();

Example response

{
  "name": "پشتیبانی اکمی",
  "greeting": "سلام! چطور می‌تونم کمکتون کنم؟"
}
Need a key or want to manage webhooks?Get in touch