Client API & Setup Guide

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.

OptionURL shapeUse when
Plain linkhttps://app.supersonik.ai/d/<demo-slug>Emails, cadences, chat, anywhere you just need a link
Embedded iframehttps://app.supersonik.ai/d/<demo-slug>/embedThe demo should run inside one of your own pages
Contextualized linkhttps://app.supersonik.ai/d/<token>You want the agent to know who the prospect is before they arrive
Voice & chat widgetA script tag on your websiteYou want a floating conversation widget with your brand's colors

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:600px on the wrapper. Give it a generous height; a cramped frame is a worse demo.
  • Fill the viewport: use height:100dvh instead. 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.

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.

RoleWhat it changes
backgroundPreview/conversation background and screen-sharing backdrop. Accepts a CSS color or gradient (e.g. linear-gradient(135deg, #ede9fe, #ffffff)). Replaces the default background.
surfaceLauncher, visitor bubbles, control bar, keycaps, and translucent chat, avatar and shared-screen surfaces. Their existing opacity is preserved.
textTitles, agent name and messages, input text, and collapsed agent name. Neutral icons also inherit it unless control_text is set.
muted_textDescriptions, empty-state text, status labels, shortcut labels, and placeholder text.
borderChat/shared-screen borders, control outlines, keycaps, scrollbars, and the focused input's subtle inset outline.
shadowShadow tint throughout the widget, with each shadow's existing opacity.
primary, primary_textPrimary 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_hoverHover color for those primary actions; otherwise a darker shade of primary.
control_background, control_hover, control_textNeutral 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.
placeholderInput placeholder override; otherwise uses muted_text.
focusKeyboard focus rings. Otherwise uses primary when customized.
linkLinks in messages. Otherwise uses primary when customized.
visitor_background, visitor_textVisitor bubble overrides; otherwise use surface and text.
speakingAgent-speaking status dot and orb/launcher glow.
listeningListening/live status dots, microphone waveform, and active microphone tint.
microphone_background, microphone_text, microphone_borderActive 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_textEnd-call action: icon color in compact layouts, or solid background and contrasting text/icons when using solid end-call controls.
danger_hoverSolid end-call control hover and border color; otherwise a darker shade of danger.
danger_background, danger_background_hoverEnd-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_textMicrophone warning badge background and contrasting text.
launcher_background, launcher_hover, launcher_textLauncher-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

MethodPathPurpose
GET/v1/client/launch-configsList your published demos and their links
POST/v1/client/demosCreate a contextualized demo link
GET/v1/client/demosList demos that have run
GET/v1/client/demos/{demo_id}Full detail for one demo
GET/v1/client/demos/{demo_id}/transcriptConversation 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

ParameterTypeDescription
statusstringWhich 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
  }
}
FieldTypeDescription
items[].idstring (UUID)Identifier of the demo configuration
items[].namestringDisplay name
items[].descriptionstring or nullOptional description
items[].urlstringFull demo link, ready to send
items[].statusstringpublished or testing
pagination.total_countintegerNumber of demos returned
pagination.max_pageintegerAlways 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.


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

HeaderRequiredValue
AuthorizationYesBearer <API_KEY>
Content-TypeYesapplication/json

Request body

FieldTypeRequiredDescription
demo_url_slugstringYesSlug 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.
contextobjectYesAny 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"
}
FieldTypeDescription
idstring (UUID)Identifier of the created link
tokenstringOpaque token embedded in the URL
urlstringThe 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

ParameterTypeRequiredDescription
pageintegerNoPage number. Defaults to 1. Must be greater than 0.
limitintegerNoResults per page. Must be greater than 0 and within the maximum configured for your integration.
start_time_fromstring (ISO 8601)NoInclusive lower bound on start_time.
start_time_tostring (ISO 8601)NoInclusive upper bound on start_time.
demo_typeprod | testNoWhich demo types to return. Repeat the parameter to select several. Defaults to prod only.
validityvalid | invalid | unknownNoWhich 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
  }
}
FieldTypeDescription
idstring (UUID)Demo identifier
typestringprod for prospect-run demos, test for internal test runs
start_timestring (ISO 8601) or nullWhen the demo started
end_timestring (ISO 8601) or nullWhen it ended. null while in progress.
duration_secondsinteger or nullDerived from start and end. null while in progress.
participant_joinedbooleanWhether anyone actually joined the call
is_validboolean or nullWhether we classified the session as valid. null if not yet evaluated.
agentobject{ "id": UUID, "name": string }
launch_configobject or null{ "id": UUID, "name": string }, matching an entry from GET /launch-configs
details_urlstringLink to the full detail resource for this demo
pagination.total_countintegerTotal demos matching the query across all pages
pagination.max_pageintegerLast 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"
}
FieldTypeDescription
agent_config_versioninteger or nullVersion of the agent configuration used. See the note below.
paramsobject or nullQuery parameters from the demo URL, merged with values the prospect entered in the pre-demo form. All values are strings.
demo_contextobject or nullThe context object supplied when the contextualized link was created. null for plain and embedded links.
participants[].display_namestringDisplay name of the participant
participants[].joined_atstring (ISO 8601)When they joined
participants[].left_atstring (ISO 8601) or nullWhen they left. null if still connected.
insights[].slugstringStable machine-readable key for the insight
insights[].namestringHuman-readable label
insights[].output_typestringValue type of this insight
insights[].valuenumber, string, boolean, array of strings, or nullThe derived value. null when not determined.
transcript_urlstringLink to the transcript resource. Requires the same API key.

agent_config_version can be null, 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"
    }
  ]
}
FieldTypeDescription
demo_idstring (UUID)Demo this transcript belongs to
messages[].rolestringWho 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[].contentstringWhat was said or typed
messages[].timestampstring or nullWhen the message occurred
messages[].sourcestring or nullvoice if spoken, chat if typed into the chat panel

role is a display label, not a stable enum. Agent messages carry the agent's configured display name, which is the same value returned as agent.name by GET /demos and GET /demos/{demo_id}, and which changes if the agent is renamed. Prospect messages carry the literal string Participant.

To classify messages reliably, either test for role == "Participant" to identify prospect turns, or fetch agent.name from the demo details and compare against it. Do not hardcode the agent name, and do not expect OpenAI-style assistant / user values.


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.

WhereWhat it can contain
params on demo detailsWhatever you put in the demo URL, commonly including email
demo_context on demo detailsWhatever you sent when creating a contextualized link
participants[].display_name on demo detailsThe prospect's display name
insights on demo detailsValues derived from the conversation
messages[].content on the transcriptThe 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.