Skip to Content
API reference

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 is ms_live_… (production) or ms_test_… (test). The project is resolved from the key — there is no projectId in 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-30 header (Stripe-style). Omitted = latest.
  • IDs: opaque strings — treat them as opaque.
  • Idempotency: the SDK generates a UUID clientId per impression/response; the server upserts on it, so offline retries never duplicate.

Status codes

CodeMeaning
200OK
304Not Modified (config, with If-None-Match)
400Malformed JSON
401Missing/invalid API key
404Unknown survey/key on ingest
422Validation error (body shape)
429Rate limited (Retry-After set)
5xxServer 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[].event is the event name (a string), not an id — the SDK matches against your analytics event names.
  • match is an equality filter on the event’s properties, AND-ed. {} = match by name alone.
  • Only ACTIVE surveys 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.

typeconfig 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.

typevalue 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.

KeyTypeMeaning
accent / accentTexthexprimary action color + its text
background / surfacehexscrim / card surface
text / secondaryTexthexprompt + helper text
borderhexdividers, chip outlines
cornerRadiusnumber | nullcustom card radius (pt). null/omitted = device-native sheet corners
controlRadiusnumberchips / buttons / choice-row radius (ignored when controlPill)
controlPillboolfull-round pill controls (radius = controlHeight / 2)
controlHeightnumberheight of the submit button, options, and scale chips
spacingnumberbase padding + layout rhythm
textScalenumberoverall multiplier for option/button/label text (title excluded)
alignment"leading" | "center"text alignment
fontstring"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.
titleSizenumberprompt/title size (pt)
titleWeight"regular" | "medium" | "semibold" | "bold"prompt/title weight
titleLineHeightnumberprompt line-height multiple (1.0 = single)
titleLetterSpacingnumberprompt 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 < samplePercent

Same user + survey → same decision every time, no server round-trip.

Identity & user properties

  • endUserId = analytics userId, falling back to deviceId. For Amplitude, read from amplitude.identity.
  • User properties for audienceMatch come from the identity snapshot (Amplitude’s cache of identify-set properties), refreshed when an $identify event flows through — not from individual event payloads. Host-supplied context via setUser(properties:) is the fallback for non-Amplitude setups.