SDK endpoints
SDK config endpoint and planned REST API.
Live endpoints
These endpoints are live and CORS-enabled — used by the JS SDK internally.
GET /api/sdk/config
Returns the Moment configuration for a project. The SDK fetches this on init to know which Moments are active.
curl https://www.usersay.ai/api/sdk/config?project=your-project-slugResponse:
{
"project": "mymap",
"productName": "MyMap",
"interviewer": {
"name": "Sarah",
"role": "Product"
},
"moments": {
"credits_exhausted": {
"enabled": true,
"triggerOnce": true,
"opener": "ugh you ran out of credits 😅 what were you working on? quick chat = 50 credits to keep going ✨",
"trigger": {
"type": "sdk_manual",
"sdkEvent": "credits_exhausted"
},
"reward": { "type": "credits", "value": "50" },
"depth": "deep",
"openMode": "bubble"
},
"pricing_visit": {
"enabled": true,
"triggerOnce": true,
"opener": "checking out pricing? lmk if anything's confusing — what are you using MyMap for?",
"trigger": {
"type": "url_match",
"urlPatterns": ["/pricing"],
"delaySeconds": 10
},
"reward": { "type": "coupon", "value": "WELCOME20" },
"depth": "deep",
"openMode": "bubble"
},
"open_feedback": {
"enabled": true,
"triggerOnce": false,
"opener": "hey 👋 what's on your mind?",
"trigger": { "type": "user_initiated" },
"depth": "quick",
"openMode": "chat"
}
}
}Response fields:
| Field | Description |
|---|---|
productName | Product name — injected into opener templates |
interviewer.name | AI persona name (default: Sarah) |
interviewer.role | AI persona role (default: Product) |
moments | Map of enabled Moments keyed by Moment ID |
moments[id].triggerOnce | Whether this Moment fires at most once per user |
moments[id].trigger.type | sdk_manual · url_match · auto_first_visit · behavior · user_initiated |
moments[id].depth | quick (light touch — close once Sarah has the headline) or deep (full interview — push for one differentiator). Conversation length is signal-based; depth tunes the closing threshold, not a turn budget. |
moments[id].openMode | bubble or chat |
Backward compat: the response also includes a duplicate
playbookskey with the same map. Older browser-cached SDKs still readplaybooks; new SDKs readmoments. The legacy key will be dropped once the cached-SDK fleet has fully rotated.
Cached for 60s with stale-while-revalidate=300.
POST /api/sdk/identify
Persists end-user identity for a project. Called by the SDK when you call UserSay.identify(). Only project and uid are required; everything else inside endUser is optional. The server will auto-fill country from the request IP if you don't pass one.
curl -X POST https://www.usersay.ai/api/sdk/identify \
-H "Content-Type: application/json" \
-d '{
"project": "mymap",
"uid": "user_123",
"endUser": {
"name": "Alice",
"email": "[email protected]",
"avatarUrl": "https://...",
"signupAt": "2026-01-15T00:00:00Z",
"plan": "pro",
"trialing": false,
"subscribed": true,
"credits": 120,
"role": "admin",
"locale": "en-US",
"country": "US",
"timezone": "America/Los_Angeles",
"traits": { "teamSize": 5 }
}
}'See the JS SDK identify reference for what each field does. Anything outside the known list is ignored — put extras in traits.
POST /api/sdk/event
Lightweight analytics endpoint for SDK funnel tracking. Called internally by the SDK.
Valid events: bubble_shown · bubble_clicked · bubble_dismissed · mini_opened · mini_closed · modal_opened · modal_closed · identify
curl -X POST https://www.usersay.ai/api/sdk/event \
-H "Content-Type: application/json" \
-d '{
"project": "mymap",
"uid": "user_123",
"event": "bubble_shown",
"trigger": "credits_exhausted"
}'The source: "agent-verify" field can be set by AI install agents (Claude Code, Cursor) to mark self-test pings — these count toward the install-connected status in the dashboard without requiring a real browser session.