API Host: https://api.supersonik.ai
App Host: https://app.supersonik.ai
Version: v1
Last Updated: 2026-10-09
To create demo links from Slack, follow the Slack app installation guide.
1. Setting up a demo
There are four ways to put a Supersonik experience in front of a prospect. Depending on your published experience, you can use more than one option.
| Option | URL shape | Use when |
|---|---|---|
| Plain link | https://app.supersonik.ai/d/<demo-slug> | Emails, cadences, chat, anywhere you just need a link |
| Embedded iframe | https://app.supersonik.ai/d/<demo-slug>/embed | The demo should run inside one of your own pages |
| Contextualized link | https://app.supersonik.ai/d/<token> | You want the agent to know who the prospect is before they arrive |
| Voice & chat widget | A script tag on your website | You want a floating conversation widget with your brand's colors |
Option A: Plain link
The simplest setup. Every published demo has a slug, and the full link is:
https://app.supersonik.ai/d/<demo-slug>
Retrieve your slugs and their full URLs from GET /v1/client/launch-configs.
The url field on each item is the ready-to-use link.
https://app.supersonik.ai/d/enterprise-demo-en
No integration work is required. Send the link and the prospect gets the full demo experience on our domain.
Option B: Embedded iframe
Runs the demo inside a page you control. Use the /embed variant of the demo path.
Replace YOUR-DEMO-SLUG with your slug from
GET /v1/client/launch-configs:
<div style="width:100%; height:600px;">
<iframe
src="https://app.supersonik.ai/d/YOUR-DEMO-SLUG/embed?native=true"
style="width:100%; height:100%; border:none;"
allow="microphone; fullscreen"
allowfullscreen
></iframe>
</div>
This is the same snippet the Supersonik app gives you from the share menu of any published demo, so you can copy it from there instead of from this document.
Required for the embed to work:
allow="microphone": the demo is a voice conversation. Without microphone permission delegated to the iframe, the prospect cannot speak to the agent.allowfullscreen: lets the prospect expand the demo, which materially improves the experience on smaller viewports.- HTTPS: browsers only grant microphone access in a secure context.
Sizing. The wrapper <div> is not decoration: it is what makes the embed work on a
page that does not set a height of its own. A percentage height on an iframe resolves
against its parent, so an iframe whose parent has an automatic height has nothing to
resolve against and collapses to its intrinsic 150px. The demo then lays itself out
inside a 150px viewport, which reads as an almost-empty strip rather than as a short
frame. Stating the height on the wrapper avoids that everywhere.
Sizing from your own code. The demo fits itself to the frame you give it, and
scrolls inside it only when the frame is too short for the form itself. If you want the frame to follow the content instead, listen
for message events whose event.origin is https://app.supersonik.ai and whose
event.source is your iframe's contentWindow. When the content is taller than the
frame, the demo sends { "source": "supersonik-embed", "type": "RESIZE", "height": 962 },
with height in CSS pixels. It never asks for less room than the frame already has,
so treat your own height as the minimum and go back to it when the frame's width
changes.
To adjust it:
- Taller or shorter: change
height:600pxon the wrapper. Give it a generous height; a cramped frame is a worse demo. - Fill the viewport: use
height:100dvhinstead. A full-viewport container, or a full-screen overlay opened from a call to action, gives the best experience. - Leave the width fluid. The demo adapts to the width of the box you give it, not to the device, so it uses your section's own width. It opens up its layout from roughly 968px wide, and again from 1200px, where the agent, the transcript and the shared screen each get room. Capping the wrapper at a few hundred pixels pins the demo to its most compact layout on every screen, including a large desktop monitor, and letterboxes the shared screen inside a narrow portrait box.
Domains. The embed page can be framed from any domain. There is no allowlist to configure on our side.
Your page's Permissions-Policy. If the hosting page sends a
Permissions-Policy header that does not permit microphone, the browser will block
the microphone even though the iframe requests it. Make sure microphone is permitted
on the page that hosts the embed.
Option C: Contextualized link
Use this when you want the agent to already know something about the prospect: their name, company, role, what they were reading, notes from a previous call. You send us the context, we return a unique link.
Call POST /v1/client/demos with the demo
slug and a context object, then send the returned url to your prospect:
https://app.supersonik.ai/d/xK9mPq2wLn4r
Contextualized links are reusable, do not expire while the underlying demo stays published, and carry immutable context. To change the context, create a new link.
Send the url exactly as returned rather than assembling one yourself. Links you
already handed out in the longer /d/c/<token> form keep working, so nothing you
have sent needs reissuing.
The Supersonik Slack app uses this same personalized-link mechanism. Run /demo
or select Home → Create custom demo link to choose a published experience,
enter a recipient email, and add optional context inside Slack. The context guides the
agent's conversation while the experience uses its normal published routing.
Personal details stay on the server, outside the URL. See the
Slack app guide for installation and account connection.
Each completed session can send an alert to the link creator's connected Slack account, updated when existing insights finish processing and subject to current Supersonik permissions. Creator attribution comes from authenticated context creation, not from a caller-supplied identity in the context.
Contextualized links are for sharing directly, not for embedding. They have no
/embed variant. To personalize a demo inside your own page, use the embed URL with
query parameters.
Option D: Voice & chat widget
For a published voice & chat experience, install the widget on your website. Set Pre-conversation description under Visitor experience in your launch configuration's Configure tab to show plain text below the agent's name before Start conversation. This text belongs to the deployment version, not the agent, and is not spoken or added to chat. Use the shared language menu above the text fields to provide copy for each conversation language. Fallback text is used when a language has no custom copy; a blank override inherits it, and an empty fallback hides the description.
In the same Visitor experience section, configure your privacy and access notice.
Use the single Disclosure text field for privacy, consent, or recording
information. Your disclosure appears directly below Start conversation and
supports line breaks and [text](https://example.com) links. Leave the field empty
to show the default Supersonik privacy notice. Country-specific form rules and lobby
auto-skip settings apply only to screen-sharing demo pages.
The same language menu controls Pre-conversation description and Disclosure text.
A blank disclosure override uses your fallback notice; an empty fallback uses the default notice.
The widget shows the notice matching the conversation language, whether selected
explicitly or matched from browser preferences. Compatible regional translations
can be reused, but different writing scripts are not interchangeable.
The disclosure does not enable recording, require a consent checkbox, or grant
browser-storage consent; manage storage consent separately with
supersonik("memory", { consent: true | false }).
Test opens the real widget using your saved configuration in the selected language, not your unsaved edits, and can run real configured tools. Selecting Fallback text tests in your agent's default language. Language changes update an unstarted widget; conversations already starting or running keep their original language. Draft and inactive configurations save automatically. For live or testing configurations, Apply live changes or Apply testing changes activates your edits immediately after confirmation.
Open Install to copy your complete snippet. Expand Widget identifier to edit its slug; changing it requires updating the snippet on your website. Integration instructions for returning-visitor memory appear when you enable it.
Replace YOUR-LAUNCH-CONFIG-SLUG with your published slug:
<script>
window.supersonik = window.supersonik || function () {
(window.supersonik.q = window.supersonik.q || []).push(arguments);
};
</script>
<script
src="https://embed.supersonik.ai/v1/loader.js"
data-launch-config-slug="YOUR-LAUNCH-CONFIG-SLUG"
data-launcher-label="Ask us anything"
data-position="bottom-right"
data-colors='{"primary":"#7c3aed","primary_text":"#ffffff","focus":"#a78bfa"}'
async
></script>
Use HTTPS for voice. If your page has a Content Security Policy, allow
https://embed.supersonik.ai in script-src, img-src, and font-src, and
https://embed-api.supersonik.ai plus your conversation's media host in
connect-src.
Choose the conversation language
The widget automatically matches the visitor's browser preferences to the selected
agent's supported languages. It checks navigator.languages in order, then
navigator.language, and falls back to the agent's primary language. Exact tags
win over compatible regional alternatives, such as es-MX using a configured
es-ES voice. Different writing scripts are not treated as interchangeable.
Set data-language="es-ES" on the loader script to choose a language explicitly,
or call:
supersonik("language", "es-ES");
supersonik("language", "auto"); // Restore browser-language matching.
Commands can be queued before the loader arrives and override data-language.
An unsupported explicit language warns in the console and uses the agent's primary
language, not another browser preference. Invalid commands leave your current
preference unchanged; an invalid script attribute uses automatic matching.
A change before Start conversation updates the selection without picking a new agent. An active or resumed conversation keeps its original language; a later command applies to the next new conversation. This also applies while a call is being prepared. The preference is not stored in cookies or localStorage, and no language selector is added. Your page's language, timezone, and location are not used unless you explicitly provide a language.
This chooses the agent's conversation language and the matching description and disclosure you configure, independently of the widget's interface language. The greeting is not changed by this setting, and your authored text is never machine-translated.
Choose the widget interface language
The launcher and widget controls use your page's <html lang> when present,
otherwise the visitor's first supported browser language. English is the fallback.
Set data-locale="es" on the loader script to override this, or queue/call:
supersonik("appearance", { locale: "es-MX" });
supersonik("appearance", { locale: null }); // Restore page/browser matching.
Regional tags fall back to their base language, so es-MX uses Spanish controls.
An unsupported explicit locale or page language uses English rather than another
browser preference. Malformed locale commands are ignored with a console warning.
Missing translations or failed translation downloads also fall back to English.
You can switch the interface language during a conversation without restarting it or losing your draft. Arabic, Persian, Hebrew, and Urdu use right-to-left layout; your configured corner does not move. Your custom launcher label remains unchanged. The interface preference is not saved in cookies or localStorage.
locale does not change the agent's spoken language, translate chat messages or
your preview description, or replace your configured end-screen translations.
Use data-language or the language command separately for the conversation.
Personalize the colors
Set data-colors to a JSON object on the script tag, or call
supersonik("appearance", { colors: { ... } }). Both use the same role names.
You can queue appearance commands before the loader arrives or update colors
while a conversation is running, without restarting it.
Colors are shared by purpose, not by individual component: titles and agent
messages share text; the launcher, visitor bubbles and control bar share
surface; Start, paused controls and Enable sound share primary.
Send, microphone and end-call buttons share neutral backgrounds and borders by
default; the end-call icon uses danger.
The same roles carry through to the collapsed call bar and screen-sharing
layout. Use the more specific roles only when you want to distinguish them.
| Role | What it changes |
|---|---|
background | Preview/conversation background and screen-sharing backdrop. Accepts a CSS color or gradient (e.g. linear-gradient(135deg, #ede9fe, #ffffff)). Replaces the default background. |
surface | Launcher, visitor bubbles, control bar, keycaps, and translucent chat, avatar and shared-screen surfaces. Their existing opacity is preserved. |
text | Titles, agent name and messages, input text, and collapsed agent name. Neutral icons also inherit it unless control_text is set. |
muted_text | Descriptions, empty-state text, status labels, shortcut labels, and placeholder text. |
border | Chat/shared-screen borders, control outlines, keycaps, scrollbars, and the focused input's subtle inset outline. |
shadow | Shadow tint throughout the widget, with each shadow's existing opacity. |
primary, primary_text | Primary action background and its contrasting text/icons: Start, retry/replay, Enable sound, the keyboard-focused Message button, and the active Pause button. Active microphone controls inherit these unless customized separately. |
primary_hover | Hover color for those primary actions; otherwise a darker shade of primary. |
control_background, control_hover, control_text | Neutral inputs, Send, microphone and pause controls and their hover/foreground colors. Also supplies the default end-call background; its icon uses danger and hovering adds a soft red tint. |
placeholder | Input placeholder override; otherwise uses muted_text. |
focus | Keyboard focus rings. Otherwise uses primary when customized. |
link | Links in messages. Otherwise uses primary when customized. |
visitor_background, visitor_text | Visitor bubble overrides; otherwise use surface and text. |
speaking | Agent-speaking status dot and orb/launcher glow. |
listening | Listening/live status dots, microphone waveform, and active microphone tint. |
microphone_background, microphone_text, microphone_border | Active microphone overrides, shared by the panel and collapsed bar. A customized listening derives the background/border and supplies the foreground; otherwise compact controls inherit primary/primary_text. |
danger, danger_text | End-call action: icon color in compact layouts, or solid background and contrasting text/icons when using solid end-call controls. |
danger_hover | Solid end-call control hover and border color; otherwise a darker shade of danger. |
danger_background, danger_background_hover | End-call background and hover overrides; the default background is neutral and the default hover is soft red. Customizing danger derives subtle end-call tints unless you explicitly override these roles. |
warning, warning_text | Microphone warning badge background and contrasting text. |
launcher_background, launcher_hover, launcher_text | Launcher-only overrides; otherwise use surface, control_hover, and text. |
Every value is a CSS color, such as #7c3aed, rgb(124 58 237), or
rebeccapurple, except background, which also accepts CSS gradients.
Image URLs are not accepted. Invalid values and
unknown role names produce a console warning and leave the current value
unchanged. Roles you omit keep the current defaults or inherit the shared role
listed above. The sphere itself is a pre-rendered image: speaking changes its
glow, not its image colors. Your agent's video and shared-screen content are
not recolored.
For example, a dark purple theme:
supersonik("appearance", {
colors: {
background: "#161321",
surface: "#252033",
text: "#f5f3ff",
muted_text: "#c4b5fd",
border: "#6d5c87",
control_background: "#302941",
control_hover: "#413754",
primary: "#7c3aed",
primary_text: "#ffffff",
focus: "#a78bfa",
link: "#93c5fd",
visitor_background: "#4c1d95",
listening: "#a78bfa",
speaking: "#93c5fd",
danger: "#fb7185"
}
});
Commands merge by role: changing primary does not discard your other colors.
Pass null for one role to remove its override, or colors: null to remove all
color overrides. Removing a specific override restores its shared-role fallback,
not necessarily the original color if you have customized that shared role.
supersonik("appearance", { colors: { primary: "#2563eb" } });
supersonik("appearance", { colors: { visitor_background: null } });
supersonik("appearance", { colors: null, accent_color: null });
The shortcut data-accent-color (or accent_color in an appearance command)
sets primary. An explicit colors.primary takes precedence; clearing it
restores the accent shortcut if one is still set. Clear accent_color too to
return completely to the default palette.
You can also change launcher_label and position (bottom-right or
bottom-left) through the appearance command. Check text/icon contrast against
their backgrounds, including hover and microphone states, when you choose a
palette. Color changes do not remove status labels or keyboard focus indicators.
Memory for returning visitors
Your voice & chat agent can remember a returning visitor's questions and preferences across conversations. In your launch configuration's Agent tab, enable Chat agent memory, then save and publish. This setting is only on or off; your SDK integration chooses how to identify visitors. Memory is off by default. An optional demo agent inherits the chat agent's memory context during a handoff.
Manual identification (default). Supply an optional, non-blank memory_id
before starting a conversation:
supersonik("identify", {
memory_id: "opaque-stable-visitor-id",
email: "jane@example.com", // Optional personalization, not the memory key.
});
You can explicitly select this behavior with data-memory-mode="manual" on the
loader script. Your integration generates and retains the identifier. Reuse the
exact same value for the same visitor; different identifiers keep memories
separate. Values are sent unchanged. For SDK-ID-based configurations, without an
ID manual mode does not load or update persistent memory. Blank or non-string
identification values are ignored with a browser-console warning. You can also
supply memory_id through context;
a non-blank value from identify takes precedence. An email supplied through
identify is personalization, not a memory key.
Automatic identification. Add these attributes to your loader script:
data-memory-mode="automatic"
data-memory-storage-key-prefix="acme-assistant"
The key prefix is optional and defaults to supersonik. After obtaining any
required storage consent, grant permission to the SDK:
supersonik("memory", { consent: true });
Before creating a new conversation, the SDK reuses or generates a random visitor
ID in your site's localStorage and sends it as memory_id. It does not read or create
persistent visitor identity before this grant or merely because someone loads your
page or opens the widget. Identification is independent of the memory toggle: after
consent, automatic mode stores and supplies an ID even when memory is disabled. The
server ignores that ID for memory lookup and updates while the toggle is off.
Withdrawing permission stops automatic identification for new conversations and
requests deletion of the stored ID:
supersonik("memory", { consent: false });
Withdrawal requests cleanup even before you open the widget or grant consent on this page. Cleanup waits for your deployment settings if they are still loading; it does not open a conversation.
The localStorage key has this structure:
<prefix>:memory:<URL-encoded API scope>:<launch-config ID>
The API scope separates backend environments; the stable launch-config ID keeps
identity intact when you change the slug or publish a version. Only the identifier
is stored, never a summary or transcript. Changing the prefix creates a separate
anonymous identity. An explicitly supplied memory_id overrides automatic
identification without overwriting the stored anonymous ID or merging memories.
Context-based tracking remains available for deployments that use other memory
keys when no memory_id is supplied. Supplying memory_id takes precedence over
that tracking. Email-keyed histories are separate from SDK-ID histories; switching
to manual or automatic SDK IDs does not transfer or merge their summaries.
Automatic recognition is limited to the same browser and site origin. It does not recognize someone on another device or subdomain. Clearing storage, private browsing, and browser privacy protections can reset identity. If persistent storage or safe cross-tab identifier creation is unavailable, the SDK warns and uses a page-lifetime identifier instead. It does not fall back to cookies or fingerprinting. Existing stored identifiers can still be reused without cross-tab creation support.
Switching visitors. Identification fields merge; omitting memory_id does not
clear it. Before logout or a user switch, reset the SDK identity:
supersonik("resetIdentity");
// For a different signed-in visitor:
supersonik("identify", { memory_id: "next-opaque-visitor-id" });
Reset ends and removes the current widget, clears its resumable session, context,
and identification, and requests removal of this deployment's automatic ID. The
launcher remains available for a fresh conversation. It does not delete server
memory. An identification change or consent change does not rebind an active or
resumed conversation. To clear an active conversation on consent withdrawal, also
call resetIdentity. Reset retains the current storage-consent grant; withdraw it
separately if permission should not carry over to the next visitor. You can queue
reset before the loader arrives; it clears the saved conversation before any resume.
If your browser blocks removal, the SDK hides the old session for the current page
and warns you. Clear it through your site's storage controls before reloading.
What is remembered. Memory carries forward a summary, not the previous transcript. Updates happen after conversation processing; an immediate new conversation may not include the just-ended conversation's summary yet. Memory is launch-config scoped unless you explicitly configure memory sharing.
Privacy and access. Use opaque, hard-to-guess identifiers, not sequential customer IDs or sensitive personal data. A memory identifier is not authentication: anyone who obtains it can present it to your public agent. Identifiers are recorded with conversations. Obtain any required consent before granting SDK storage access; localStorage is not exempt from storage/access rules merely because it is not a cookie. Use memory only with an appropriate lawful basis and accepted exposure.
2. Query parameters
You can append query parameters to a plain link, an embed URL, or a contextualized link. They serve two purposes:
Attribution. Tag each placement so you can tell which campaign, email, or page drove a session:
https://app.supersonik.ai/d/enterprise-demo-en?utm_medium=email&utm_campaign=spring-spotlight
https://app.supersonik.ai/d/enterprise-demo-en?utm_medium=email&utm_persona=1
Personalization. Pass what you already know so the prospect does not have to
type it. Passing email is the most common case, and it can remove a form step:
https://app.supersonik.ai/d/enterprise-demo-en?email=jane.smith@acme.com
If your demo is configured to use URL-based email memory, pass the prospect's
email as utm_email:
https://app.supersonik.ai/d/enterprise-demo-en?utm_email=jane.smith@acme.com
The platform normalizes and hashes this value before using it as a memory key, so the same prospect can return to their existing memory for that launch config. The raw value is still visible in the URL and is recorded with the demo; anyone who knows the email can present it, so only use this option when you have an appropriate lawful basis and have accepted that exposure.
Keep visitors on the post-call page. For a native demo, add disableRedirect=1
to the URL to prevent automatic post-call and feedback-submit redirects. Explicit
links on the post-call page remain available. This parameter works only with
native=true; it has no effect with native=false.
https://app.supersonik.ai/d/enterprise-demo-en/embed?native=true&disableRedirect=1
How they come back to you. Every parameter on the URL is recorded against the
demo and returned in the params object of
GET /v1/client/demos/{demo_id}, merged with any values the
prospect entered in the pre-demo form. Parameter names are yours to choose. Use
whatever labels fit your reporting.
Using launch information in HTTP tools. Your HTTP tools can pass launch
information to an external workflow, such as an n8n webhook, alongside the current
view and conversation transcript. Add "context": {{context}} to a JSON request
body to include the available context, or select fields such as
{{context.recipient}}, {{context.sender}}, {{context.transcript}} and
{{context.launch}}. Nothing is added to existing requests automatically.
Recipient and sender identities preserve values supplied through your configured
URL mappings or the pre-demo form, rather than substituting enriched names.
Your configured mappings also apply to submitted form parameters. A submitted
form value takes precedence over the URL value for the same parameter.
Unavailable identity fields are null. Enriched lead attributes are provided
separately. The transcript contains the most recent 200 conversation turns;
transcript_truncated tells your workflow whether older turns were omitted.
The view URL is null for internal viewers and URLs containing credentials, query
parameters or fragments. If a tool cannot capture its required context, the
request is not sent.
The context can include personal information and contextualized-link data, so
send it only to endpoints authorized to receive that data. Tool credentials and
platform access tokens are not part of the context. You can test the payload with
fictional context in the tool editor before attaching the tool to your agent.
Query and form data shared with HTTP tools. context.launch.params includes
only email, work_email, enrichment_email, sender_enrichment_email,
first_name, last_name, name, recipient_first_name, recipient_last_name,
sender_first_name, sender_last_name, company, company_name, company_size,
phone, interest, seats, language, timezone, weight_group, utm_source,
utm_medium, utm_campaign, utm_content and utm_term. Form values and field
metadata are included only when a configured form field uses one of those names.
Other parameters can still serve your existing demo configuration but are not
forwarded through this tool context.
Query parameters vs. contextualized links. Query parameters are the right tool for a handful of flat, non-sensitive values, and they are visible in the URL. A contextualized link is the right tool for richer or more sensitive data: the payload travels server-to-server and only an opaque token appears in the URL.
3. Authentication
All API requests require a Bearer token.
Authorization: Bearer <API_KEY>
The API key is issued by the Supersonik team. It:
- Authenticates your requests
- Identifies your organization
- Scopes every response to your organization's data only
Data belonging to other organizations is never visible through this API.
Keep your API key secure. It is a server-side credential. Do not expose it in client-side code, in a browser, or in a public repository.
4. Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /v1/client/launch-configs | List your published demos and their links |
POST | /v1/client/demos | Create a contextualized demo link |
GET | /v1/client/demos | List demos that have run |
GET | /v1/client/demos/{demo_id} | Full detail for one demo |
GET | /v1/client/demos/{demo_id}/transcript | Conversation transcript for one demo |
4.1 List published demos
GET /v1/client/launch-configs
Returns every published demo configuration for your organization, with a ready-to-use link for each. Only screen-sharing demos are listed. Voice & chat experiences (the embedded website widget) have no demo link and never appear here. The full set comes back in one response; this endpoint is not paginated.
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Which demos to return: published or testing. Repeat the parameter to select both. Defaults to published. Any other value is rejected with 422. |
A testing demo is one your team is trialling against a version of the product tour that has not gone live yet. It has a working link and runs like any other demo, but it is not part of what you publish to prospects, so it is left out unless you ask for it.
Response: 200 OK
{
"items": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Product Demo - Enterprise",
"description": "Interactive demo showcasing enterprise features",
"url": "https://app.supersonik.ai/d/enterprise-demo-en",
"status": "published"
},
{
"id": "7cb92a31-8824-4a91-bc12-1d847e22ab91",
"name": "Product Demo - Starter",
"description": "Quick tour of starter plan capabilities",
"url": "https://app.supersonik.ai/d/starter-demo-en",
"status": "published"
}
],
"pagination": {
"total_count": 2,
"max_page": 1
}
}
| Field | Type | Description |
|---|---|---|
items[].id | string (UUID) | Identifier of the demo configuration |
items[].name | string | Display name |
items[].description | string or null | Optional description |
items[].url | string | Full demo link, ready to send |
items[].status | string | published or testing |
pagination.total_count | integer | Number of demos returned |
pagination.max_page | integer | Always 1; this endpoint is not paginated |
The slug in url is the <demo-slug> used by the embed path and by
demo_url_slug when creating a contextualized link. A testing demo's slug works
the same way.
Demos that run from a testing link are recorded with type test, so they do not
appear in GET /v1/client/demos unless you pass demo_type=test.
4.2 Create a contextualized demo link
POST /v1/client/demos
Generates a unique demo URL with your context data attached. When the prospect opens it, the agent runs the demo with that context available.
Headers
| Header | Required | Value |
|---|---|---|
Authorization | Yes | Bearer <API_KEY> |
Content-Type | Yes | application/json |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
demo_url_slug | string | Yes | Slug of the demo to run. Must be a published, screen-sharing demo belonging to your organization; a voice & chat experience's slug is rejected with 400 Bad Request. |
context | object | Yes | Any JSON object. No fixed schema. |
{
"demo_url_slug": "enterprise-demo-en",
"context": {
"prospect_name": "Jane Smith",
"company": "Acme Corp",
"role": "VP of Engineering",
"interests": ["CI/CD pipelines", "security scanning"],
"account_tier": "enterprise",
"meeting_notes": "Interested in migrating from Jenkins. Team of 50 developers."
}
}
Context limits. The context object must not exceed 32 KB serialized, and must
not nest more than 5 levels deep. Requests that exceed either limit are rejected
with 400 Bad Request.
Context shared with HTTP tools. Your full context remains available to the demo,
but HTTP tools receive only approved fields through context.launch.demo_context:
string values for prospect_name, company, role, account_tier, meeting_notes,
source, creator_id and opportunity_context; string arrays for interests and
presentation_order; and campaign as either a string or an object containing
string id, name, source, medium, content, term and a string array
segments. Other keys and unexpected nested objects are omitted. Keep credentials
out of these descriptive fields; their contents are not a general secret filter.
This does not change the context stored with your link or returned in demo details.
Response: 201 Created
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"token": "xK9mPq2wLn4r",
"url": "https://app.supersonik.ai/d/xK9mPq2wLn4r"
}
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Identifier of the created link |
token | string | Opaque token embedded in the URL |
url | string | The link to send to your prospect |
Link behaviour
- Reusable: can be opened multiple times; each visit starts a new session with the same context.
- No expiry: stays valid while the underlying demo remains published.
- Immutable context: create a new link to change the context.
Errors
400 Bad Request: the body is invalid, the context exceeds the size or depth limit,
demo_url_slug does not match a published demo for your organization, or the slug
belongs to a voice & chat experience.
{
"error": "BadRequest",
"detail": "No published demo found for demo_url_slug 'invalid-slug'"
}
4.3 List demos
GET /v1/client/demos
Returns a paginated, newest-first list of demos that have run for your organization. Use this to sync demo activity into your CRM or warehouse, detect newly completed demos, or backfill history.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number. Defaults to 1. Must be greater than 0. |
limit | integer | No | Results per page. Must be greater than 0 and within the maximum configured for your integration. |
start_time_from | string (ISO 8601) | No | Inclusive lower bound on start_time. |
start_time_to | string (ISO 8601) | No | Inclusive upper bound on start_time. |
demo_type | prod | test | No | Which demo types to return. Repeat the parameter to select several. Defaults to prod only. |
validity | valid | invalid | unknown | No | Which validity verdicts to return. Repeat the parameter to select several. Defaults to valid and unknown, i.e. everything we have not classified as invalid. |
Results are ordered newest-first by start_time, then by demo ID descending so that
ordering is stable when timestamps tie. Both date filters are inclusive.
Default filtering. With no demo_type or validity parameters you get production
demos that we have not classified as invalid. Internal test runs and demos where nobody
engaged are excluded, which is what a CRM or warehouse sync usually wants. Pass the
parameters explicitly to widen the selection:
GET /v1/client/demos # prod, valid + unknown
GET /v1/client/demos?demo_type=prod&demo_type=test # include internal test runs
GET /v1/client/demos?validity=valid&validity=invalid&validity=unknown # every verdict
GET /v1/client/demos?validity=unknown # only demos with no verdict
unknown is part of the default on purpose: is_valid is written when post-call
processing finishes, so a demo that is still running, or still being processed, has no
verdict yet. Excluding unknown would hide those demos until processing completes, and
a sync that advances start_time_from past them in the meantime would never see them.
Response: 200 OK
{
"items": [
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"type": "prod",
"start_time": "2026-05-04T09:30:02.000000Z",
"end_time": "2026-05-04T09:41:14.000000Z",
"duration_seconds": 672,
"participant_joined": true,
"is_valid": true,
"agent": {
"id": "b2c3d4e5-6789-01bc-def0-234567890abc",
"name": "Enterprise Sales Agent"
},
"launch_config": {
"id": "c3d4e5f6-7890-12cd-ef01-34567890abcd",
"name": "Enterprise Demo - EN"
},
"details_url": "https://api.supersonik.ai/v1/client/demos/a1b2c3d4-5678-90ab-cdef-1234567890ab"
}
],
"pagination": {
"total_count": 42,
"max_page": 5
}
}
| Field | Type | Description |
|---|---|---|
id | string (UUID) | Demo identifier |
type | string | prod for prospect-run demos, test for internal test runs |
start_time | string (ISO 8601) or null | When the demo started |
end_time | string (ISO 8601) or null | When it ended. null while in progress. |
duration_seconds | integer or null | Derived from start and end. null while in progress. |
participant_joined | boolean | Whether anyone actually joined the call |
is_valid | boolean or null | Whether we classified the session as valid. null if not yet evaluated. |
agent | object | { "id": UUID, "name": string } |
launch_config | object or null | { "id": UUID, "name": string }, matching an entry from GET /launch-configs |
details_url | string | Link to the full detail resource for this demo |
pagination.total_count | integer | Total demos matching the query across all pages |
pagination.max_page | integer | Last available page for the current limit |
Incremental sync. Store the latest start_time you have processed and use it as
the next start_time_from. Because the filter is inclusive, de-duplicate by demo id
when polling overlapping windows.
Errors
400 Bad Request: start_time_from is after start_time_to.
{
"error": "BadRequest",
"detail": "start_time_from must not be after start_time_to"
}
4.4 Get demo details
GET /v1/client/demos/{demo_id}
Full detail for a single demo: everything from the list view, plus the URL parameters and context it ran with, who joined, the insights we derived, and a link to the transcript.
Response: 200 OK
{
"id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"type": "prod",
"start_time": "2026-05-04T09:30:02.000000Z",
"end_time": "2026-05-04T09:41:14.000000Z",
"duration_seconds": 672,
"participant_joined": true,
"is_valid": true,
"agent": {
"id": "b2c3d4e5-6789-01bc-def0-234567890abc",
"name": "Enterprise Sales Agent"
},
"agent_config_version": 8,
"launch_config": {
"id": "c3d4e5f6-7890-12cd-ef01-34567890abcd",
"name": "Enterprise Demo - EN"
},
"params": {
"email": "jane.smith@acme.com",
"utm_medium": "email",
"utm_campaign": "spring-spotlight"
},
"demo_context": {
"prospect_name": "Jane Smith",
"company": "Acme Corp"
},
"participants": [
{
"display_name": "Jane Smith",
"joined_at": "2026-05-04T09:30:11.000000Z",
"left_at": "2026-05-04T09:41:09.000000Z"
}
],
"insights": [
{
"slug": "interest_level",
"name": "Interest level",
"output_type": "string",
"value": "high"
},
{
"slug": "requested_trial",
"name": "Requested a trial",
"output_type": "boolean",
"value": true
}
],
"transcript_url": "https://api.supersonik.ai/v1/client/demos/a1b2c3d4-5678-90ab-cdef-1234567890ab/transcript"
}
| Field | Type | Description |
|---|---|---|
agent_config_version | integer or null | Version of the agent configuration used. See the note below. |
params | object or null | Query parameters from the demo URL, merged with values the prospect entered in the pre-demo form. All values are strings. |
demo_context | object or null | The context object supplied when the contextualized link was created. null for plain and embedded links. |
participants[].display_name | string | Display name of the participant |
participants[].joined_at | string (ISO 8601) | When they joined |
participants[].left_at | string (ISO 8601) or null | When they left. null if still connected. |
insights[].slug | string | Stable machine-readable key for the insight |
insights[].name | string | Human-readable label |
insights[].output_type | string | Value type of this insight |
insights[].value | number, string, boolean, array of strings, or null | The derived value. null when not determined. |
transcript_url | string | Link to the transcript resource. Requires the same API key. |
agent_config_versioncan benull, permanently. Demos pinned to a configuration that predates version numbering have no version to report and never will. This is not a gap that gets backfilled. Treat the field as nullable in your pipeline.
Fields shared with the list response (id, type, start_time, end_time,
duration_seconds, participant_joined, is_valid, agent, launch_config) carry
the same meaning as documented in 4.3.
4.5 Get demo transcript
GET /v1/client/demos/{demo_id}/transcript
The full conversation for one demo, in order.
Response: 200 OK
{
"demo_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"messages": [
{
"role": "Enterprise Sales Agent",
"content": "Hi Jane, thanks for joining. Want me to start with the pipeline view?",
"timestamp": "2026-05-04T09:30:14.000000Z",
"source": "voice"
},
{
"role": "Participant",
"content": "Yes please, and show me the security scanning after.",
"timestamp": "2026-05-04T09:30:21.000000Z",
"source": "voice"
},
{
"role": "Participant",
"content": "can you share pricing?",
"timestamp": "2026-05-04T09:36:02.000000Z",
"source": "chat"
}
]
}
| Field | Type | Description |
|---|---|---|
demo_id | string (UUID) | Demo this transcript belongs to |
messages[].role | string | Who spoke. Agent turns carry the agent's configured display name; prospect turns carry the literal Participant. See the note below before branching on this value. |
messages[].content | string | What was said or typed |
messages[].timestamp | string or null | When the message occurred |
messages[].source | string or null | voice if spoken, chat if typed into the chat panel |
roleis a display label, not a stable enum. Agent messages carry the agent's configured display name, which is the same value returned asagent.namebyGET /demosandGET /demos/{demo_id}, and which changes if the agent is renamed. Prospect messages carry the literal stringParticipant.To classify messages reliably, either test for
role == "Participant"to identify prospect turns, or fetchagent.namefrom the demo details and compare against it. Do not hardcode the agent name, and do not expect OpenAI-styleassistant/uservalues.
5. Shared error responses
All endpoints can return the following.
401 Unauthorized: the API key is missing, invalid, or expired.
{
"error": "NotAuthenticated",
"detail": "Invalid or missing authentication token"
}
Depending on which layer rejects the request, this may instead arrive as
{"detail": "Missing authentication credentials"} or {"detail": "Invalid Bearer token"}.
Treat any 401 the same way.
404 Not Found: the requested demo_id does not exist, or does not belong to your
organization.
422 Unprocessable Entity: a query parameter or path parameter failed validation,
for example a malformed datetime, page=0, or a demo_id that is not a valid UUID.
{
"detail": [
{
"type": "greater_than",
"loc": ["query", "page"],
"msg": "Input should be greater than 0",
"input": "0",
"ctx": {"gt": 0}
}
]
}
500 Internal Server Error: an unexpected server error.
{
"error": "InternalServerError",
"detail": "An unexpected error occurred"
}
6. Data returned by the API
Some responses contain personal data about your prospects. Handle them according to your own data policies.
| Where | What it can contain |
|---|---|
params on demo details | Whatever you put in the demo URL, commonly including email |
demo_context on demo details | Whatever you sent when creating a contextualized link |
participants[].display_name on demo details | The prospect's display name |
insights on demo details | Values derived from the conversation |
messages[].content on the transcript | The full verbatim conversation, spoken and typed |
The list endpoints (GET /launch-configs, GET /demos) do not return params,
demo_context, or transcript content. If you only need activity volumes and timings,
the list endpoint is sufficient and avoids pulling personal data.
7. Rate limits and support
Rate limits. Contact the Supersonik team for the limits applicable to your integration.
Support. For questions or issues, contact the Supersonik team.