Skip to main content
The dashboard endpoints back everything at admin.insito.app. They’re public and stable — use them to build internal tooling, custom dashboards, or to migrate data out of Insito. Base URL: 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).
Platform is one of 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 planbranding.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:
Behavior fields (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:
Use { "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:
The 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:
  • answers is 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.
  • nextCursor is null on the last page. Pass it back as cursor to fetch the next page. Pagination is keyset-based on (submitted_at, id), so it stays stable as new responses arrive.
  • totalCount is the exact total for the survey; it’s only returned on the first page (null on 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-8
  • Content-Disposition: attachment; filename="{survey-slug}-responses-{YYYY-MM-DD}.csv"
The first row is a header built from the response metadata (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.
Manual variables (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 from identify({ 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_id in URL path params. API keys are proj_<32 hex>.
  • All routes 404 on cross-org access — no 403, so org existence isn’t leaked.

Status & uptime

We publish realtime status at status.insito.app and incidents at status.insito.app/incidents. Webhooks update both within seconds of an incident open/close.