U
usersay.ai
/Docs

Getting Started

Add UserSay to your product in under 5 minutes.

UserSay is an embeddable AI interview widget that talks to your users at the moments that matter — when they hit a credit limit, after their first week on a paid plan, or right before they churn. You get structured insights, they get a reward.

Quick Overview

  1. Create a project in the UserSay dashboard and configure your research agenda
  2. Add the SDK script to your app — one <script> tag
  3. Identify the user after login so the SDK knows who they are
  4. Fire triggers at the right moments — SDK opens the interview modal automatically
  5. Handle completion — grant rewards via onComplete callback

Integration Method

MethodStatusBest for
JS SDK (script tag)ReleasedAny web app — vanilla JS, Vue, Svelte, React
React Component (@usersay/react)PlannedReact / Next.js apps preferring a component API
REST API (api.usersay.ai)PlannedMobile apps, server-side triggers, headless usage

Minimal Example

The fastest way to get started — paste this before </body>:

<!-- Step 1: Load the SDK -->
<script src="https://usersay.ai/usersay-sdk.js"
        data-project="your-project-slug" async></script>

<!-- Step 2: Identify user + fire trigger -->
<script>
  UserSay.identify({ uid: currentUser.id, plan: currentUser.plan });

  // Fire when the moment is right
  if (currentUser.credits <= 0) {
    UserSay.trigger('credits_exhausted');
  }
</script>

The SDK handles the rest: opens a modal with the AI interview, deduplicates (won't re-show after dismiss or completion), and fires your onComplete callback when the interview finishes.

Next Steps

JS SDK

Add UserSay to any web app with a single script tag.

Released — The JS SDK is live and available at https://usersay.ai/usersay-sdk.js.

The JS SDK is the primary way to integrate UserSay. It works with any web framework — no build step, no npm install required.

Installation

Add the script tag to your app (once, in <head> or before </body>):

<script src="https://usersay.ai/usersay-sdk.js"
        data-project="your-project-slug" async></script>

Replace your-project-slug with your project's slug (e.g. mymap). You can find it in your project settings.

Step 1 — Identify the User

Call UserSay.identify() after the user logs in. This tells the SDK who the current user is and enables deduplication (the same user won't see the interview twice).

UserSay.identify({
  uid: currentUser.id,      // required — your user's unique ID
  plan: currentUser.plan,   // optional — 'free', 'pro', etc.
  credits: currentUser.credits  // optional — current credit balance
});
FieldTypeRequiredDescription
uidstringYesUnique user ID — used for deduplication
planstringNoCurrent plan (e.g. "free", "pro")
creditsnumberNoCurrent credit balance

Step 2 — Fire a Trigger

Call UserSay.trigger() at the moment you want the interview to appear. The SDK opens a modal with the AI interview automatically.

// When credits run out
if (currentUser.credits <= 0) {
  UserSay.trigger('credits_exhausted');
}

// Day 7 of a paid plan
if (daysSinceUpgrade(currentUser) === 7) {
  UserSay.trigger('day_7_paid');
}

// User visited /pricing
if (location.pathname === '/pricing') {
  UserSay.trigger('pricing');
}

// Any custom moment
UserSay.trigger('completed_onboarding');

The trigger name is passed to the AI as context — it shapes the opening question. See Triggers for the list of built-in trigger contexts.

Step 3 — Handle Completion

Register a callback to run when the user finishes the interview:

UserSay.onComplete(function() {
  // Grant reward — do this server-side for security
  fetch('/api/grant-credits', { method: 'POST' });

  // Or show a toast
  showToast('Thanks! 50 credits have been added.');
});

Note: For granting real credits or discounts, always enforce rewards server-side. The onComplete callback fires client-side and should be used for UI feedback only (toasts, banners). See Reward Callbacks.

Full Example

<!-- In your app layout -->
<script src="https://usersay.ai/usersay-sdk.js"
        data-project="mymap" async></script>

<script>
  // After user logs in
  UserSay.identify({
    uid: currentUser.id,
    plan: currentUser.plan,
    credits: currentUser.credits
  });

  // Register completion handler
  UserSay.onComplete(function() {
    fetch('/api/grant-interview-reward', { method: 'POST' });
  });

  // Fire trigger when appropriate
  if (currentUser.credits <= 0) {
    UserSay.trigger('credits_exhausted');
  }
</script>

SDK API Reference

MethodDescription
UserSay.identify({ uid, plan?, credits? })Identify the current user. Call after login.
UserSay.trigger(eventName)Fire a trigger — opens the interview modal.
UserSay.open(triggerName?)Open the modal directly, bypassing dedup rules.
UserSay.close()Close the modal programmatically.
UserSay.onComplete(fn)Register a callback for interview completion.

Script Tag Attributes

AttributeRequiredDescription
data-projectYesYour project slug (e.g. mymap)
data-hostNoOverride base URL — useful for self-hosting

Deduplication

The SDK automatically prevents re-showing the interview:

  • Completed — if the user finished the interview, it will never show again for that user/project combination
  • Dismissed — if the user closed without completing, the interview is suppressed for 7 days

Dedup state is stored in localStorage under usersay:{project}:{uid}:completed and usersay:{project}:{uid}:dismissed.

Async Loading

Because the script tag has async, your UserSay.identify() and UserSay.trigger() calls may run before the SDK has loaded. The SDK handles this automatically — calls made before load are queued and replayed once the script initializes.

Next Steps

  • Triggers — full list of trigger contexts and what they tell the AI
  • Reward Callbacks — server-side reward fulfillment

React Component

Integrate UserSay into React and Next.js apps.

Planned — The @usersay/react package is not yet published. For React and Next.js apps today, use the JS SDK via next/script or a plain <script> tag.

Using the JS SDK in React / Next.js today

The JS SDK works fine in React and Next.js apps. Load it with next/script and call the API from your components:

// app/layout.tsx (or _app.tsx)
import Script from 'next/script';

export default function Layout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://usersay.ai/usersay-sdk.js"
          data-project="your-project-slug"
          strategy="afterInteractive"
          onLoad={() => {
            window.UserSay?.identify({
              uid: getCurrentUser().id,
              plan: getCurrentUser().plan,
            });
          }}
        />
      </body>
    </html>
  );
}

Then trigger from any client component:

'use client';

export function CreditsBanner() {
  function handleCreditsTrigger() {
    window.UserSay?.trigger('credits_exhausted');
  }

  return (
    <div>
      You've used all your credits.
      <button onClick={handleCreditsTrigger}>
        Chat with us for 50 free credits
      </button>
    </div>
  );
}

Planned: @usersay/react

The React package will provide a more idiomatic API:

// Planned API — not yet available
import { UserSayWidget } from '@usersay/react';

export default function App() {
  return (
    <UserSayWidget
      projectId="proj_xxx"
      user={{ id: currentUser.id, plan: currentUser.plan }}
      onComplete={() => grantCredits(currentUser.id, 50)}
    />
  );
}

And a hook:

// Planned API — not yet available
'use client';
import { useUserSay } from '@usersay/react';

export function CreditsBanner() {
  const { trigger } = useUserSay();
  return (
    <button onClick={() => trigger('credits_exhausted')}>
      Chat with us for 50 free credits
    </button>
  );
}

In the meantime, use the JS SDK directly.

Triggers

Configure when UserSay interviews are shown to your users.

Triggers define when an interview invitation appears. UserSay uses lifecycle-based triggers — interviews happen at moments that naturally prompt reflection, not random popups.

How Triggers Work

  1. Your app calls UserSay.trigger('event_name') at the right moment
  2. The SDK checks dedup — skips if the user already completed or was recently dismissed
  3. If clear, the SDK opens a modal with the AI interview
  4. The trigger name is passed to the AI as context — it shapes the opening question

Built-in Trigger Contexts

These trigger names have built-in AI openers — the AI knows how to start the conversation naturally based on the context.

TriggerKeyAI Opening Context
Credits Exhaustedcredits_exhaustedUser just hit their usage limit while working on something
Day 7 Paidday_7_paidUser has been on a paid plan for one week — knows the product well
InactiveinactiveUser hasn't been active for 7+ days after regular usage
Visited PricingpricingUser just visited the pricing page — thinking about money
Customcustom (default)Generic opener — AI introduces itself and asks what the user was trying to do

Custom Trigger Names

You can pass any string to UserSay.trigger(). If it doesn't match a built-in context, the AI uses a generic opener.

// Built-in context — AI knows how to open this
UserSay.trigger('credits_exhausted');

// Custom event — AI uses generic opener
UserSay.trigger('completed_first_export');
UserSay.trigger('cancelled_subscription');
UserSay.trigger('hit_api_limit');

Example: Wiring Triggers in Your App

// After page load / user auth
UserSay.identify({ uid: user.id, plan: user.plan, credits: user.credits });
UserSay.onComplete(() => grantReward(user.id));

// Credits ran out
if (user.credits <= 0) {
  UserSay.trigger('credits_exhausted');
}

// Day 7 of paid plan
if (user.plan !== 'free' && daysSinceUpgrade(user) === 7) {
  UserSay.trigger('day_7_paid');
}

// Visiting /pricing
if (location.pathname === '/pricing') {
  UserSay.trigger('pricing');
}

// Long inactivity (check on login)
if (daysSinceLastActive(user) >= 7) {
  UserSay.trigger('inactive');
}

Deduplication

The SDK prevents the same user from seeing the interview multiple times:

  • Completed → never show again
  • Dismissed → suppress for 7 days

This is handled automatically in localStorage. You don't need to track this yourself.


Planned: Dashboard-Configured Triggers

Planned — Currently triggers are fired manually from your code. Dashboard-based auto-triggers are on the roadmap.

In a future release, you'll be able to configure trigger rules directly in the UserSay dashboard — no code changes needed:

{
  "triggers": [
    {
      "key": "credits_exhausted",
      "enabled": true,
      "cooldown_days": 30,
      "max_per_user": 1
    },
    {
      "key": "time_on_page",
      "enabled": true,
      "seconds": 600
    },
    {
      "key": "url_match",
      "enabled": true,
      "patterns": ["/pricing", "/upgrade"]
    }
  ]
}

The SDK already includes the engine for time-on-page and URL-match triggers — just waiting on dashboard UI to configure them.

Next Steps

Reward Callbacks

Grant users credits, discounts, or other incentives after completing an interview.

Rewards incentivize users to complete interviews. When a user finishes a conversation, you grant the reward from your app.

How It Works Today

The SDK fires a client-side callback when the interview completes. Use this to trigger a server-side reward grant from your app.

UserSay.onComplete(function() {
  // Call your own API to grant the reward
  fetch('/api/grant-interview-reward', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: currentUser.id })
  });
});

Your server endpoint grants the reward:

// app/api/grant-interview-reward/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  const session = await getSession(req); // your auth
  if (!session?.user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  await grantCredits(session.user.id, 50);
  return NextResponse.json({ ok: true });
}

Important: Always enforce rewards server-side. The onComplete callback fires in the browser and should not be the sole gatekeeper — a user could trigger it manually. Use it to initiate a server call; validate and grant the reward on your backend.

Reward Types

Configure what reward users receive in your Setup page:

TypeExampleWhen to use
Credits+50 creditsIn-product currency (most common)
Coupon codeTHANKS20Discount on next billing cycle
Custom link/redeem?token=xxxAnything else — gift cards, swag, etc.

The reward type and value are passed to the AI, which mentions it naturally at the end of the interview ("Your 50 credits have been added!").

Client-Side Feedback

Show a toast or notification to the user after the interview completes:

UserSay.onComplete(function() {
  // Fire server-side grant
  fetch('/api/grant-interview-reward', { method: 'POST' });

  // Show UI feedback
  showToast('Thanks for sharing! 50 credits have been added.');
});

Planned: Webhooks

Planned — Server-to-server webhook delivery is on the roadmap.

In a future release, UserSay will POST directly to your server when an interview completes — no client-side callback needed:

POST https://yourapp.com/api/usersay/reward

{
  "event": "interview.completed",
  "session": {
    "id": "sess_abc123",
    "user_id": "user_123",
    "trigger": "credits_exhausted",
    "completed_at": "2026-04-14T10:30:00Z"
  }
}

This is more reliable than the client-side callback (works even if the user closes the tab) and easier to secure with signature verification.

Next Steps

  • Triggers — configure which moments trigger interviews
  • API Reference — manage sessions programmatically (planned)

API Reference

Server-side API for creating sessions, sending messages, and retrieving insights.

Planned — The api.usersay.ai REST API is not yet available. This page documents the planned API for reference.

The UserSay REST API will let you create and manage interview sessions programmatically — useful for mobile apps, server-side triggers, or custom interview UIs.

What's Available Today

EndpointStatusDescription
GET /api/sdk/config?project={slug}ReleasedReturns trigger rules for the JS SDK. CORS-enabled.
POST /api/research/chatReleasedStreaming chat endpoint used internally by the interview widget
api.usersay.ai/v1/*PlannedFull REST API for sessions, insights, and project management

GET /api/sdk/config

Used by the JS SDK to fetch trigger configuration on init.

curl https://usersay.ai/api/sdk/config?project=mymap
{
  "project": "mymap",
  "triggers": {
    "credits_exhausted": { "enabled": true, "type": "event" },
    "day_7_paid": { "enabled": true, "type": "event" }
  },
  "reward": { "type": "credits", "value": "50" }
}

Planned: Full REST API

The following endpoints are planned and not yet available.

Authentication

curl -H "Authorization: Bearer sk_xxx" \
  https://api.usersay.ai/v1/...

Create a Session

POST /v1/sessions
{
  "project_id": "proj_xxx",
  "user_id": "user_123",
  "trigger": "credits_exhausted"
}

Send a Message

POST /v1/sessions/{session_id}/message
{ "content": "I was building a flowchart for a client." }

Get Session + Transcript

GET /v1/sessions/{session_id}

Get Structured Insight

GET /v1/sessions/{session_id}/insight

Returns structured fields extracted from the completed interview (user role, JTBD, main blocker, willingness to pay, etc.).

List Sessions

GET /v1/sessions?project_id=proj_xxx&status=completed&limit=20

If you need server-side integration before the API ships, reach out — we can discuss options.