https://api.insito.app/v1/dashboard. See
Authentication for the Supabase JWT bearer
flow.
Projects
GET /projects
List projects for the authenticated user’s org. The response is wrapped in a
projects array and uses the snake_case column names from the database.
POST /projects
Create a project. Triggers the welcome email on org’s first project
(see Welcome email runbook).
react_native (default), ios, android, flutter.
GET /projects/:id
Single project plus a respondent_count (number of identified users).
PATCH /projects/:id
Update an existing project. All fields optional; send only what changed.
brandConfig is the project Brand Kit. The server
merges it onto canonical defaults and clamps it to the org’s plan —
branding.showPoweredBy is forced to true below the Growth plan. It is then
returned to the SDK in the survey response (see
POST /event).
themeMode ("light" / "dark" / "auto") selects the survey appearance and
colorsDark holds the dark-scheme palette (INS-104). Both fields are optional;
omitted fields keep their stored value (or the canonical default — themeMode
defaults to "light").
labels (INS-135) are the project’s SDK UI label
overrides. Send only the keys that changed: a string sets the override, and
null clears it back to the SDK default. Other keys keep their stored value.
Empty / whitespace-only strings are dropped (never stored). See
SDK labels for the full key list.
autoDiscoverEvents and autoDiscoverScreens (INS-160) are independent
booleans (default true) controlling whether the SDK ingestion endpoints
auto-register new events / screens into the variable registry. When false for
a type, new sdk_auto entries of that type stop being created but “last seen”
keeps updating on existing ones; declared and manual variables are unaffected.
DELETE /projects/:id
Soft-delete — sets is_active = false and returns { "ok": true }. It does
not cascade-delete surveys, respondents, or responses. Use the dashboard’s
confirmation flow rather than this endpoint directly.
Surveys
GET /projects/:projectId/surveys
List surveys for a project, ordered by created_at desc.
POST /projects/:projectId/surveys
Create a survey. Body matches the creating-surveys
shape — name, triggerKey, throttleDays, audience, plus a questions
array (1–20 items total, including welcome and end screens).
PATCH /surveys/:id
Partial update. Builder edits write to the survey draft JSON column only
— live SDK content is unchanged until you call Publish.
Status-only patches ({ "status": "active" } with no other fields) update
live columns immediately (used from the survey list).
Draft PATCH response:
showBranding, showQuestionNumbers, allowBack,
autosaveProgress) are clamped to the org’s plan on save.
POST /surveys/:id/publish
Copy draft → live columns and set published_at. Optional body:
{ "status": "active" } when activating a never-published draft.
For active/paused surveys, omit status to publish content without
changing eligibility.
Response:
DELETE /surveys/:id
Hard delete — removes the survey row (and its responses). Returns
{ "ok": true }. To deactivate without deleting, PATCH { "status": "paused" }.
GET /surveys/:id/analytics
NPS analytics for the survey’s first NPS question, powering the dashboard cards
and trend chart. Response is wrapped in analytics:
trend array holds daily UTC buckets for the last 30 days. If the survey has
no NPS question, the counts come back as zeros.
Responses
GET /projects/:projectId/responses
Offset-paginated response list across the project.
Query params:
Response is
{ responses, total, limit, offset }.
GET /surveys/:id/responses
Cursor-paginated responses for a single survey, newest first. Powers
the Survey Builder Responses tab. Includes both completed and
partial responses.
Query params:
answersis a map keyed by question id (not an array). A question that’s absent from the map was skipped or never reached (branching) — render it as empty / an em dash.nextCursorisnullon the last page. Pass it back ascursorto fetch the next page. Pagination is keyset-based on(submitted_at, id), so it stays stable as new responses arrive.totalCountis the exact total for the survey; it’s only returned on the first page (nullon subsequent pages).
GET /surveys/:id/responses/export
Streams the survey’s full response set as text/csv (no pagination,
no plan gating). Returns:
Content-Type: text/csv; charset=utf-8Content-Disposition: attachment; filename="{survey-slug}-responses-{YYYY-MM-DD}.csv"
Submitted At, Status, Platform, App Version, Respondent ID) followed by one
column per (non-screen) question, using the question text. Each
subsequent row is one response. The Respondent ID column contains the SDK
userId (external_user_id), not the internal UUID. Fields are RFC-4180
escaped (values containing commas, quotes, or newlines are wrapped in double
quotes with interior quotes doubled). Multiple-choice answers are joined with
; , and skipped questions are empty cells. A UTF-8 BOM is prepended so
spreadsheet apps detect the encoding.
Integrations
Integrations are project-scoped. Each project stores at most one row per type (slack, webhook, email_digest).
GET /projects/:projectId/integrations
Returns the project’s configured integrations.
POST /projects/:projectId/integrations
Upserts an integration. The body is a discriminated union on type:
DELETE /projects/:projectId/integrations/:type
Removes the integration of the given type.
POST /projects/:projectId/integrations/slack/connect
Kicks off the Slack OAuth flow. Returns { "url": "https://slack.com/oauth/..." }
to send the browser to; the callback handles the rest.
Variables
The App Variable Registry — the project’s events and screens. Auto-discovered (sdk_auto) and SDK-declared (sdk_init) entries are
created by the SDK endpoints; the routes below manage
manual entries and labels/descriptions.
GET /projects/:projectId/variables
Lists variables for the project, each with type, name, display_label,
description, source, verified, approved, first_seen_at, last_seen_at,
and created_at. Optional query params: type (event | screen) and
approved (true | false) to filter the Available vs Auto-discovered lists.
POST /projects/:projectId/variables
Creates a manual variable. Returns 409 if (type, name) already exists.
POST) are created with approved: true; auto-discovered
(sdk_auto) entries start approved: false and only appear in the survey
builder’s trigger picker once approved.
PATCH /projects/:projectId/variables/:varId
Updates name, displayLabel, and/or description. Send at least one field.
Renaming name cascades the change into every survey that references the key
(live and draft configurations) via the rename_app_variable RPC.
PATCH /projects/:projectId/variables/:varId/approve
Approves a single auto-discovered variable so it becomes selectable in the
builder.
POST /projects/:projectId/variables/approve-all
Approves all currently-unapproved variables. Optional type query param scopes
it to event or screen.
GET /projects/:projectId/variables/:varId/usage
Returns the surveys that reference the variable, so the UI can warn before a
rename or delete.
DELETE /projects/:projectId/variables/:varId
Deletes any variable, regardless of source. SDK-reported names may reappear in
the Auto-discovered list on the next ingestion.
User properties
The User Properties registry — the custom, business-level properties discovered fromidentify({ properties }). Properties are discovery-only:
they’re created and typed by the SDK, so there’s no create or delete route.
GET /projects/:projectId/user-properties
Lists every discovered property, each with name, type
(string | number | boolean), display_label, description,
first_seen_at, last_seen_at, and an approximate user_count.
PATCH /projects/:projectId/user-properties/:name
Updates displayLabel and/or description only. Send at least one field. The
:name path segment is the property key (URL-encoded).
Billing
GET /billing/plans
Returns the plan catalog (free, starter, growth, studio) with prices,
response limits, app limits, and features.
POST /billing/checkout
Creates a Stripe Checkout session for a plan upgrade. Returns { "url": "..." }
— redirect the user there.
POST /billing/portal
Creates a Stripe customer portal session. Returns { url } for downgrades,
cancellation, and card management.
GET /billing/invoices
Lists the org’s Stripe invoices.
Additional add-on routes exist for purchasable extra apps:
POST /billing/checkout-addon and POST /billing/cancel-addon.
Usage
There is no/billing/usage route. Use:
GET /organization/usage→{ "usage": { "plan", "year", "month", "responseCount", "responseLimit", "percent" } }(org aggregate).GET /projects/:id/usage→ per-app usage (the value the SDK cap is checked against).
Organization
GET /organization
Current org row, wrapped in organization: id, name, slug, plan,
plan_app_limit, plan_response_limit, usage_context, goals,
stripe_customer_id, stripe_subscription_id, created_at, updated_at. It
does not embed a member list.
PATCH /organization
Edit org-level fields. Optional body: name, usageContext (work |
personal | null), and goals (array, max 5). Members manage their own
profile via PATCH /members/me.
Conventions
- Every endpoint returns JSON, even errors.
- Pagination varies: the project-level response list uses
limit/offset; the per-survey response list uses cursor (cursor,limit,nextCursor). - Timestamps are ISO 8601 in UTC.
- Primary keys are UUIDv4. Projects and surveys also accept their
short_idin URL path params. API keys areproj_<32 hex>. - All routes 404 on cross-org access — no 403, so org existence isn’t leaked.