Client API & Setup Guide

API Host: https://api.supersonik.ai App Host: https://app.supersonik.ai Version: v1 Last Updated: 2026-08-03


1. Setting up a demo

There are three ways to put a Supersonik demo in front of a prospect. They are not mutually exclusive. You can use all three against the same demo.

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/c/<token>You want the agent to know who the prospect is before they arrive

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="camera; 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.
  • allow="camera": only needed if your demo is configured to show a camera preview before the call. If it is not, use allow="microphone; fullscreen".
  • 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.

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/c/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.

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.


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

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.

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. This endpoint takes no query parameters and returns the full set in one response.

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"
    },
    {
      "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"
    }
  ],
  "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
pagination.total_countintegerNumber of published demos
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.


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 demo belonging to your organization.
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.

Response: 201 Created

{
  "id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "token": "xK9mPq2wLn4r",
  "url": "https://app.supersonik.ai/d/c/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, or demo_url_slug does not match a published demo for your organization.

{
  "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.