API reference
The contract between the SDK and the MicroSurveys backend. You normally don’t call these endpoints yourself — the SDK does — but this documents the exact shapes it exchanges.
Conventions
- Base URL:
https://microsurveys.edubai.ventures - All SDK routes are under
/api/sdk/*. - Auth:
Authorization: Bearer <project_api_key>, where the key isms_live_…(production) orms_test_…(test). The project is resolved from the key — there is noprojectIdin request bodies. - Content type:
application/json; charset=utf-8. - Timestamps: ISO-8601 UTC (e.g.
2026-06-30T12:00:03Z). - Versioning: date-based via the
MS-Api-Version: 2026-06-30header (Stripe-style). Omitted = latest. - IDs: opaque strings — treat them as opaque.
- Idempotency: the SDK generates a UUID
clientIdper impression/response; the server upserts on it, so offline retries never duplicate.
Status codes
| Code | Meaning |
|---|---|
200 | OK |
304 | Not Modified (config, with If-None-Match) |
400 | Malformed JSON |
401 | Missing/invalid API key |
404 | Unknown survey/key on ingest |
422 | Validation error (body shape) |
429 | Rate limited (Retry-After set) |
5xx | Server error — retry with backoff |
Error body: { "error": { "code": "invalid_api_key", "message": "…" } }
GET /api/sdk/config
Returns all active surveys for the project with everything needed to evaluate triggers and render surveys locally/offline. The SDK caches this to disk and refreshes on app start and on foreground (throttled to once per 12 hours), using the ETag.
Request headers: Authorization, optional If-None-Match: <etag>.
Response 200 (also sets ETag and Cache-Control):
{
"projectId": "prj_abc",
"fetchedAt": "2026-06-30T12:00:00Z",
// Project-level appearance theme, configured in the dashboard. Applied by the
// SDK automatically. All keys optional; {} = SDK defaults. (See Theme below.)
"theme": {
"accent": "#4F46E5",
"accentText": "#FFFFFF",
"background": "#FFFFFF",
"surface": "#FFFFFF",
"text": "#18181B",
"secondaryText": "#71717A",
"border": "#E4E4E7",
"cornerRadius": null, // null/omitted = device-native sheet corners
"controlRadius": 12,
"controlPill": false,
"controlHeight": 48,
"spacing": 16,
"textScale": 1,
"alignment": "leading", // "leading" | "center"
"font": "system", // "system" | a curated Google font
"titleSize": 20,
"titleWeight": "semibold", // "regular" | "medium" | "semibold" | "bold"
"titleLineHeight": 1.1,
"titleLetterSpacing": 0
},
"surveys": [
{
"id": "svy_wallet_ces",
"name": "Wallet CES",
"dismissible": true, // false = required (no close button, no swipe/scrim dismiss)
"presentation": null, // "sheet" | "dialog" | null (inherit theme position)
"successMessage": null, // custom completion text; null = SDK default
// Eligibility (all evaluated SDK-side)
"audienceMatch": { "plan": "pro" }, // user-property equality filter, AND-ed; {} = all
"samplePercent": 100, // 0-100, deterministic per user
"maxPerUserDays": 30, // per-user cap; 0 = no cap
"startsAt": null, // ISO-8601 or null
"endsAt": null,
// Triggers: any matching trigger fires the survey
"triggers": [
{
"id": "trg_1",
"type": "SEQUENCE", // "SINGLE" | "SEQUENCE"
"delaySeconds": 3, // wait after satisfied; 0 = immediate
"sequenceWindowSeconds": 60, // SEQUENCE only; null = no limit
"fireEvery": 1, // fire every Nth satisfied occurrence
"warmupCount": 0, // ignore first N satisfied occurrences
"steps": [
{ "order": 0, "event": "page_view", "match": { "screen": "Wallet" } },
{ "order": 1, "event": "wallet_tap", "match": {} }
]
}
],
// Questions (ordered)
"questions": [
{
"id": "qst_1",
"order": 0,
"type": "CES",
"prompt": "How easy was it to understand your wallet balance and payment methods?",
"required": true,
"config": { "min": 1, "max": 7, "minLabel": "Very difficult", "maxLabel": "Very easy" }
}
]
}
]
}Notes
steps[].eventis the event name (a string), not an id — the SDK matches against your analytics event names.matchis an equality filter on the event’s properties, AND-ed.{}= match by name alone.- Only
ACTIVEsurveys are returned; paused/draft/archived are omitted.
POST /api/sdk/impressions
Records that a survey was shown (for response-rate analytics and cap state). Sent
when the survey closes, so dismissed is final. Batched and offline-safe.
{
"impressions": [
{
"clientId": "1f1d…uuid", // idempotency key (UUID v4)
"surveyId": "svy_wallet_ces",
"triggerId": "trg_1", // optional: which trigger fired
"endUserId": "amp_user_123",
"shownAt": "2026-06-30T12:00:03Z",
"dismissed": false // true = closed without answering
}
]
}Response 200: { "accepted": 1 }
POST /api/sdk/responses
Submits answers. Batched and idempotent. A response may be completed: false if
the user dropped partway through a multi-step survey.
{
"responses": [
{
"clientId": "9a2c…uuid",
"surveyId": "svy_wallet_ces",
"endUserId": "amp_user_123",
"completed": true,
"submittedAt": "2026-06-30T12:00:30Z",
"userProps": { "plan": "pro", "locale": "en-US" }, // snapshot for context
"answers": [
{ "questionId": "qst_1", "value": { "value": 6 } }
]
}
]
}Response 200: { "accepted": 1 }
Question config shapes
What the builder writes and the SDK renders from, by question type.
type | config shape |
|---|---|
NPS | { "min": 0, "max": 10, "minLabel"?: string, "maxLabel"?: string } |
CES | { "min": 1, "max": 7, "minLabel"?: string, "maxLabel"?: string } |
CSAT_STAR | { "count": 5 } |
CSAT_EMOJI | { "options": [{ "value": string, "emoji": string, "label"?: string }] } |
THUMBS | {} |
SINGLE_CHOICE | { "options": [{ "value": string, "label": string }] } |
OPEN_TEXT | { "placeholder"?: string, "maxLength"?: number } |
Answer value shapes
What the SDK submits in answers[].value.
type | value shape |
|---|---|
NPS / CES / CSAT_STAR | { "value": number } |
THUMBS | { "value": "up" | "down" } |
CSAT_EMOJI | { "value": string } (the chosen option’s value) |
SINGLE_CHOICE | { "choice": string } (the chosen option’s value) |
OPEN_TEXT | { "text": string } |
Theme
The project-level appearance theme delivered in the config. The SDK maps it onto
its SurveyTheme at init — no host code required. All keys optional; unknown
keys ignored; {} = the SDK’s default theme.
| Key | Type | Meaning |
|---|---|---|
accent / accentText | hex | primary action color + its text |
background / surface | hex | scrim / card surface |
text / secondaryText | hex | prompt + helper text |
border | hex | dividers, chip outlines |
cornerRadius | number | null | custom card radius (pt). null/omitted = device-native sheet corners |
controlRadius | number | chips / buttons / choice-row radius (ignored when controlPill) |
controlPill | bool | full-round pill controls (radius = controlHeight / 2) |
controlHeight | number | height of the submit button, options, and scale chips |
spacing | number | base padding + layout rhythm |
textScale | number | overall multiplier for option/button/label text (title excluded) |
alignment | "leading" | "center" | text alignment |
font | string | "system" or a curated Google font: Inter, Roboto, Open Sans, Noto Sans, Lato, Montserrat, Poppins. Fetched at runtime and registered in memory — never bundled, never written to disk. Until loaded, renders in the system font. |
titleSize | number | prompt/title size (pt) |
titleWeight | "regular" | "medium" | "semibold" | "bold" | prompt/title weight |
titleLineHeight | number | prompt line-height multiple (1.0 = single) |
titleLetterSpacing | number | prompt tracking (pt) |
Sheet vs. dialog is a per-survey setting (survey.presentation), not a theme
key. Theming is project-wide for MVP; per-survey overrides are a future
enhancement.
Eligibility & sampling
The full eligibility order is evaluated on-device: window → trigger → frequency → audience → sampling → cap.
Sampling is deterministic and sticky:
inSample = stableHash(endUserId + ":" + surveyId) % 100 < samplePercentSame user + survey → same decision every time, no server round-trip.
Identity & user properties
endUserId= analyticsuserId, falling back todeviceId. For Amplitude, read fromamplitude.identity.- User properties for
audienceMatchcome from the identity snapshot (Amplitude’s cache of identify-set properties), refreshed when an$identifyevent flows through — not from individual event payloads. Host-supplied context viasetUser(properties:)is the fallback for non-Amplitude setups.