AI OPERATOR · INTEGRATION GUIDE

Your platform, with AI support.

Use the headless API with your current support system, or use RoamDesk for the inbox, conversation tickets and messaging channels. Both share the partner account’s AI allowance.

1. Keep customer verification in your backend

Authenticate your customer and check ownership before reading your eSIM provider, orders or billing system. Build a fresh, bounded snapshot for that customer. Never forward a browser-supplied customer ID, order, usage reading or navigation URL without checking it. Omit private facts for a guest.

The API accepts a live READ, ORDERS or FULL Partner API key. Sign requests using the existing HMAC contract. Keep the secret on your server. Requests are limited to 64 KiB, 12 messages of 2,000 characters and 30 calls per minute per key, shared with /assistant/chat.

The API examples in Partner API documentation include signing instructions. A copyable Node.js client is also supplied in examples/ai-operator-client.mjs in the repository; it signs the full path and query, uses distinct request signatures and never automatically retries a paid request.

Copy the server-side Node.js client
/** Server-only Node.js example. Keep secrets and customer verification here. */
import { createHash, createHmac, randomUUID } from "node:crypto";

export function createAiOperatorClient({
  keyId,
  secret,
  origin = "https://roamgo.net",
  fetchImpl = fetch,
}) {
  const base = new URL(origin);
  if (base.protocol !== "https:" || base.username || base.password || base.pathname !== "/" || base.search || base.hash) {
    throw new Error("Use a plain HTTPS API origin.");
  }
  if (!keyId || !secret) throw new Error("A server-side API key and secret are required.");
  return {
    async respond(input) {
      const url = new URL("/api/partner/v1/assistant/respond", base);
      // Distinct signatures even for identical questions in the same second.
      url.searchParams.set("request", randomUUID());
      const raw = JSON.stringify(input);
      if (Buffer.byteLength(raw, "utf8") > 64 * 1024) throw new Error("Snapshot exceeds 64 KiB.");
      const ts = Math.floor(Date.now() / 1000);
      const bodyHash = createHash("sha256").update(raw).digest("hex");
      const canonical = [ts, "POST", url.pathname + url.search, bodyHash].join("\n");
      const signature = createHmac("sha256", secret).update(canonical).digest("hex");
      const response = await fetchImpl(url, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Roamgo-HMAC-SHA256 keyId=${keyId}, ts=${ts}, sig=${signature}`,
        },
        body: raw,
        signal: AbortSignal.timeout(150_000),
        cache: "no-store",
      });
      // No automatic retry: a timeout may already have spent model tokens.
      // The host application falls back to its agents on any failure.
      if (!response.ok) throw new Error(`AI Operator unavailable (${response.status}); route to your support team.`);
      return response.json();
    },
  };
}

2. Send your verified facts

POST /api/partner/v1/assistant/respond accepts facts from any provider through your backend adapter. It does not call a third-party provider or accept its credentials. Send only the information relevant to the current question.

Fictional request. Replace the references and observation dates on every request.

{
  "messages": [
    {
      "role": "user",
      "content": "How much data is left?"
    }
  ],
  "locale": "en",
  "capabilities": {
    "cards": true
  },
  "context": {
    "observedAt": "2026-10-12T12:00:00Z",
    "customer": {
      "id": "customer_123",
      "verified": true
    },
    "usage": [
      {
        "id": "esim_123",
        "label": "Japan 5 GB",
        "usedMb": 1200,
        "totalMb": 5000,
        "updatedAt": "2026-10-12T11:57:00Z",
        "expiresAt": null,
        "status": "ACTIVE",
        "daily": [
          {
            "date": "2026-10-12",
            "usedMb": 200,
            "partial": true
          }
        ]
      }
    ]
  }
}
Optional snapshot collections
CollectionMaximumFields
plans12id, title, coverage[], dataMb (number or null), validityDays, price {amountMinor, currency}, purchaseUrl
topups10The plan fields plus esimId; only top-ups your backend verified as eligible
orders10id, label, planTitle, status, manageUrl, optional invoiceUrl
usage10id, label, usedMb, totalMb, updatedAt, expiresAt, status; up to 31 daily {date, usedMb, partial} observations
links6id, kind (ACCOUNT, PAYMENT_METHOD, INSTALLATION), title, url

Orders, usage, top-ups and account links require customer: { id, verified: true }, which is your signed backend’s attestation. It grants no access to a RoamGo account. Prices use integer minor units in the given currency. Use offset-qualified ISO timestamps and UTC YYYY-MM-DD dates for daily usage. Observation timestamps and daily usage dates are checked against a five-minute clock-skew window. Null means unknown; it never means unlimited or zero usage. Navigation URLs must be HTTPS without embedded credentials.

3. Render cards and handle human takeover

Set capabilities.cards only if your support client renders the typed card payloads. The model selects up to three known card references; the server returns the original validated data and URLs. Use escaped text, your own brand theme and HTTPS navigation. Never render model text as HTML or execute an action based on it.

{
  "answer": "Your last reported usage was 1.2 GB out of 5 GB, leaving 3.8 GB at the time of that reading.",
  "scope": "partner_customer",
  "cards": [
    {
      "id": "usage:esim_123",
      "type": "usage",
      "data": {
        "id": "esim_123",
        "label": "Japan 5 GB",
        "usedMb": 1200,
        "totalMb": 5000,
        "updatedAt": "2026-10-12T11:57:00Z",
        "expiresAt": null,
        "status": "ACTIVE",
        "daily": [
          {
            "date": "2026-10-12",
            "usedMb": 200,
            "partial": true
          }
        ],
        "remainingMb": 3800,
        "stale": false
      }
    }
  ],
  "handoff": {
    "required": false,
    "reason": null
  },
  "customerReference": "customer_123",
  "observedAt": "2026-10-12T12:00:00Z"
}

When handoff.required is true, pause your automated replies and route the existing conversation, including its history, to your agents. Reasons are customer_request, account_action, policy, uncertain and unresolved. requestHuman: true guarantees that signal on a successful response. On a timeout or any non-2xx response, fall back to your team. This endpoint creates no ticket and sends no customer message.

Usage is an observed snapshot, never a live carrier check. Cards label readings older than 15 minutes as stale, preserve gaps and partial days, and show zero usable data on expired or exhausted bundles. Links open your secure checkout, invoice or account flow; chat never buys, refunds, cancels, installs an eSIM or saves a payment method.

Conversation history stays in your support system. RoamGo meters every model round against the shared allowance and does not store the submitted snapshot as a transcript. The configured AI provider processes the messages and relevant tool data; see the trust page.

RoamDesk + AI Operator

Open the setup path, confirm pilot access, create or select an owned workspace, add your channels and team, and review its AI settings. RoamDesk supplies the shared inbox, conversation status, assignments, notes, reply suggestions, translation and optional automatic replies. Its seats and connections use a separate workspace plan; AI uses the owner’s partner allowance.

RoamGo support linking and external provider account lookups are separate integrations. The headless snapshot contract can support your own adapter, but enabling RoamDesk AI alone does not give a channel access to an external provider account. Pilot and billing switches remain enforced.

Existing RoamGo integrations

/assistant/chat keeps its existing support-only contract. For RoamGo-sold orders, customer tokens and the drop-in widget remain available through /assistant/customer-tokens and /assistant/customer-chat. Those tokens cannot read another provider’s orders. The new headless API is server-to-server and works with your own card renderer and helpdesk.

Download the full OpenAPI contract