Reference

Web SDK

How the copilot behaves on a website — the tag, version pinning, signed-in visitors, allowed domains, dark mode, CSP, actions, screens and events.

The web SDK is one JavaScript file with no dependencies. It does not care what your site is built with, and it speaks the same protocol as the Flutter SDK: the same components, the same actions from the console, the same persona. One app in the console can serve your mobile app and your website with the same key.

Set up with AI adds it: your coding agent puts the tag on every page, wires sign-in, registers screens and fills in the console. This page is what that leaves in your site and how the SDK behaves, for when you change it later. The live key works once the app is live, which the setup does by itself when its checks pass; until then it is refused with app_not_live and the site runs on the test key.

To change any of it, start from a task: each opens with the prompt for your coding agent. This page is what that prompt relies on.

The tag

index.html
<!-- Inside <head>, on every page. Swap in the live key when the site goes live.
     data-app-key: your app's key — Rendel console → Settings → Keys & environments → Copy.
     data-languages: "tr" Turkish only, "tr,en" Turkish or English,
     "en" English only. Leave it out to answer in whatever the visitor writes. -->
<script
  src="https://rendel.ai/sdk/web/0.23.4/rendel.js"
  data-app-key="rd_pk_test_…"
  data-languages="tr"
></script>

It sits inside <head> on every page; in Next.js it is a <Script> with strategy="beforeInteractive" in the root layout. The tag also reads data-locale and data-api-base-url, and data-launcher="false" hides the corner button that opens the copilot.

Pinning

The version is in the address. 0.23.4 stays 0.23.4 after the next release ships, so your site changes only when you change the number. The changelog says what each version brings.

Who is signed in

your sign-in code
// Right after your sign-in succeeds:
Rendel.identify({ userId: user.id });

// On every page: the signed-in person's current token, or null, so the
// copilot can reach your API as them — no API key.
Rendel.setUserToken(() => getAccessToken());

// When the user signs out:
Rendel.reset();

setUserToken is how the copilot answers from your own API — "my order", "my routine" — as the person asking, with no API key to issue: in the console, those endpoints are set to the signed-in user's token. The function is asked before every question, so it returns the current token (refreshed there if it has run out), or null when nobody is signed in. It goes on to your endpoints as Authorization: Bearer, is never stored, and each person reaches only what their own token opens. It is called on every page, since a function cannot be remembered across loads.

An endpoint that changes something — "book me in on Tuesday" — is asked on a confirmation card first. Once the person confirms, Rendel makes the call as them and answers with what happened; there is no handler to write on the page.

userId is the id your own backend knows the person by — the same one your mobile app passes, so a person has one history across both. The SDK remembers them across page loads and visits, so a returning visitor needs no second call. traits ({ plan: "pro" }) carries anything the copilot should know about them, and userHmac is needed when identity verification is on.

Without these, every visitor is anonymous and their conversations belong to the browser. That is the right default for a site with no sign-in.

The conversation follows the visitor from page to page within the tab: a link to another page of your site does not start it over.

Allowed domains

The test key works on localhost and 127.0.0.1, on any port, straight away. Before the site goes live, add its domain in the console under Settings → Security → Allowed domains. A key in a web page can be read by anyone who opens its source; the list is what stops it working on their site. Until the domain is listed, the live key answers that site with origin_not_allowed.

musteri.com means https://musteri.com. www.musteri.com is a separate entry, and *.musteri.com covers every subdomain. Your mobile apps send no domain and are not affected by the list.

Dark mode

With the default themeMode: "system", the panel follows your page first and the browser second: a data-theme="light" or data-theme="dark" on <html> (or else <body>), which is what most sites' own appearance switches set, and without one the visitor's prefers-color-scheme. Both are watched, so a switch repaints the panel. "light" or "dark" is final, and Rendel.setThemeMode(...) changes the mode while the page runs. The dark colours are darkTheme, or derived from theme when it is left out; see Map your brand to the catalog.

Permissions

Nothing to add to any file. The "+" offers Photos and Files, which open the browser's own file picker (pictures; PDFs and text files), and Camera, which the browser asks the visitor for the first time it is used. The microphone uses the browser's speech recognition — the browser asks the visitor for the microphone the first time it is pressed. It opens voice mode: what the visitor says is written in the middle of the panel as they say it, and sent on its own when they stop.

  • The microphone needs https (localhost counts). Firefox has no speech recognition, so it is not shown there; everything else works.
  • Inside an <iframe>, the frame needs allow="microphone; camera".
  • If your site sends a Content-Security-Policy, add these sources to its directives:
Content-Security-Policy
script-src https://rendel.ai;
connect-src https://api.rendel.ai https://tiles.openfreemap.org;
img-src https: blob:;
worker-src blob:;

tiles.openfreemap.org and worker-src blob: are for the map block (0.20.0 and later). The map engine loads from rendel.ai beside rendel.js, and because a browser will not start a worker from another origin, it runs its worker from a blob: URL. If your policy also sets style-src or default-src, add https://rendel.ai there for the map's stylesheet. blob: in img-src is for photo previews and your logo.

Actions

Actions you connect in the console run on our servers and work on the website with no code. An action that should run in the page, as the signed-in visitor, is registered the way the Flutter SDK registers one:

app.js
Rendel.registerAction({
  name: "get_order_status",
  description: "Fetch the delivery status of an order",
  params: {
    type: "object",
    properties: { order_id: { type: "string" } },
    required: ["order_id"],
  },
  risk: "read",
  handler: async ({ order_id }) => {
    const response = await fetch(`/api/orders/${order_id}`);
    return response.json();
  },
});

params is JSON Schema. Return what the copilot should know, or throw to say it failed; keep the result under 16 KB. write and destructive actions get the same server-built confirmation card as on a phone, and confirmTemplate words it.

Screens

A screen of your site the copilot may offer on a screens card:

app.js
Rendel.registerRoute({
  name: "order_detail",
  title: "My orders › Order",
  description: "One order: its items, delivery and payment.",
  params: { order_id: { type: "string", required: true, fromActions: true } },
  open: ({ order_id }) => router.push(`/orders/${order_id}`),
});

A tap shrinks the panel into a bubble and calls open. The bubble brings the panel back. bubbleOffset (px; 104 on a phone-wide page, 24 otherwise) keeps the bubble above your own bottom bar.

From 0.15.0. A param marked fromActions must equal a value one of your actions returned in this conversation. Names are snake_case; a title is 1–60 characters, a description up to 300; up to 8 params and 50 routes.

Options

The data attributes cover the common case. For the rest, leave data-app-key off the tag and call init yourself:

app.js
Rendel.init({
  appKey: "rd_pk_test_…",
  languages: ["tr"],
  assistantName: "Pati",
  suggestions: ["Milo bugün nasıl?", "Aşı takvimini göster"],
  launcher: false, // your own button: Rendel.open()
});
Option
languages, localeWhat it answers in; locale is your site's language picker.
assistantName, launcherLabel, suggestions, emptyStateTitle, emptyStateBody, composerHintThe words on screen.
stringsYour own words for the SDK's chrome, by key (confirm, goBack, irreversible, composerHint, launcher, …), laid over the shipped set for the answer language; a key you leave out keeps the shipped translation. rendel.d.ts exports the Strings interface, so a misspelt key is a type error.
attachIcon, voiceIcon, sendIconThe composer's three glyphs. "off" removes attach or voice.
themesurface, onSurface, accent, onAccent and the rest of the console's appearance roles.
themeMode, darkThemesystem (the default), light or dark, and the colours for dark. See Dark mode.
launcherfalse hides the corner button. Open it with Rendel.open(), close() or toggle().
enabledfalse turns the copilot off and wins over anything published.
remoteConfigfalse ignores what is published in the console.
actionsThe same as registerAction, all at once.
userTokenThe same as Rendel.setUserToken.
onOpenUrlRoute a card's link yourself instead of opening a new tab.
bubbleOffsetpx above the bottom edge for the bubble while a screen the copilot opened is showing: 104 on a phone-wide page, 24 otherwise.
apiBaseUrl, streamIdleTimeoutMsWhere it calls, and how long a turn may go silent (ms, default 45000).
onLogReceives the SDK's warnings instead of the browser console. See Log codes.

Appearance and the opening surface published from the console override these, on the website and the app alike, without a release.

Rendel.on("opened" | "closed" | "messageSent" | "actionExecuted", fn) subscribes to what happens; Rendel.deviceId is the anonymous id to quote in a support ticket. Rendel.isOpen, Rendel.isInitialized and Rendel.version report state. Rendel.refreshConfig() re-reads the published appearance. Rendel.pair(code) pairs a test browser with the console (Pair a test app).

Log codes

What onLog receives, in record.code. The code is stable and safe to switch on; message is for humans and may be reworded.

codeWhen
already_initializedinit was called again with a different appKey; the first one stands.
invalid_action / invalid_routeA registered action or route was malformed and skipped; the message names the field.
invalid_useridentify needs a userId of 1 to 128 characters.
insecure_pageActions and routes need an https page (or localhost).
theme_contrastA theme colour pair failed contrast.
listener_failedAn on(...) listener threw.
handler_failedAn action handler threw, or returned something that is not JSON.
handler_timeoutA handler passed its timeoutMs.
result_too_largeA handler returned more than 16 KB; the copilot is told it failed.
context_provider_timeout / context_provider_failedA context provider missed its 300 ms budget, or threw.
route_open_failedA route's open threw (route_failed before 0.21.1).
manifest_sync_failedYour actions and routes did not reach the server, or it rejected them. Tried again the next time the panel opens.
transport_failedA network call failed.
config_unreadableThe published config could not be read, so it was ignored.
surface_staleDebug: a block arrived for a surface_id with a revision no newer than the one on screen, and was not drawn.
pairedDebug: pair was accepted.
pair_test_only / pair_not_found / pair_rate_limited / pair_failedWhy pair did not pair: a live key; a wrong, used or expired code; too many tries in a minute; anything else.

A turn the server ends with an error is logged under that error's own code (see Errors). A key that is not a key is reported as invalid_key in the browser console whatever onLog is, because the copilot does not start.

TypeScript

Types are published beside the file. Save rendel.d.ts into your sources and window.Rendel is typed.