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 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:
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. Without a date range it spans the last 30 days; with from/to it spans the requested window. If the survey has no NPS question, the counts come back as zeros. Query params (shared by all three analytics reads below):

GET /surveys/:id/analytics/completion

Completion funnel for the survey, wrapped in completion: views, starts, completed, completionRate, avgTimeToCompleteSeconds, medianTimeToCompleteSeconds, avgQuestionsAnsweredIncomplete, and a branching-aware questionFunnel array (questionId, questionText, reached, answered, dropOffRate). Accepts the same from/to params.

GET /surveys/:id/analytics/results

Per-question answer distributions powering the Analytics Results section. Accepts the same from/to params. Response is wrapped in results — an array with one entry per answerable question (in position order):
Each entry carries type, text, position, answeredCount, and skippedCount. The remaining fields depend on type:
  • nps — distribution (counts for scores 0–10) plus promoters, passives, detractors.
  • rating — minValue, maxValue, average, and distribution ({ value, count } per point on the scale).
  • multiple_choice — allowMultiple and options ({ label, count } per choice, with a trailing Other bucket when present).
  • open_text — totalResponses and responses ({ value, submittedAt }, newest first, capped at 100).

POST /surveys/:id/reports/export-pdf

Renders the survey Analytics report to a PDF and streams it back (Content-Type: application/pdf, Content-Disposition: attachment). Optional JSON body { "from": "…", "to": "…" } scopes the report to a date range. The PDF is a point-in-time snapshot styled with the app’s Brand Kit. Returns 503 pdf_unavailable when server-side rendering isn’t configured.

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, retention_days, 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), goals (array, max 5), and retentionDays (integer, 30 to 3650; see Retention). Members manage their own profile via PATCH /members/me.

DELETE /organization

Deletes the organisation and everything it owns: apps, surveys, responses, end users, shared report links, API keys and member logins. The Stripe subscription is cancelled first; if that fails, nothing is deleted (502 subscription_cancel_failed). Body: { "confirm": "<organisation slug>" }, or 400 confirmation_mismatch. Returns { "deleted": true, "projects", "users", ... }. Dashboard session only.

DELETE /account

Deletes the signed-in user’s login. Body: { "confirm": "<account email>" } (case-insensitive). When the user is the organisation’s only member, the organisation is deleted as in DELETE /organization and the response has "organizationDeleted": true. Dashboard session only.

End-user data requests

For access and erasure requests from your app’s users. See Privacy and data requests. These routes need a dashboard session: API keys and OAuth tokens get 403.

GET /projects/:projectId/respondents/:externalUserId

Exports everything stored for one end user in the app, by the userId the SDK sends. Returns { "export": { "respondent", "responses", "questions", "impressions", "exportedAt" } }. With ?format=csv, returns one CSV row per answer (response_id, survey, status, submitted_at, question, answer, skipped). 404 respondent_not_found when nothing is stored. URL-encode the user ID.

DELETE /projects/:projectId/respondents/:externalUserId

Erases the end user’s profile, responses, impressions and throttle stamps in the app. Body: { "confirm": "<externalUserId>" }. Returns { "erased", "responses", "impressions" }; repeating it returns "erased": false.

Audit log

A record of security-relevant changes in the organisation: API keys and AI tool access, shared report links, app and survey deletion, SDK key rotation, billing changes, integrations, retention changes and end-user data requests. Entries are written by Insito, can’t be edited or deleted, and are kept for 2 years. Dashboard session only: API keys and OAuth tokens get 403 session_required.

GET /audit-log

Newest first. Query: limit (1–100, default 50), before (the nextCursor from the previous page), optional from / to (ISO 8601). Returns { "entries": [...], "nextCursor": number | null }. Each entry has id, createdAt, action (for example api_token.created), result, actor (type of user, token or system, plus the current email and token or AI tool name), target ({ type, id }), metadata and ip. End users appear only as a hash of their user ID, never the ID itself. With ?format=csv, returns up to 10,000 entries as CSV (time, action, result, actor, actor_type, target_type, target_id, ip, details). Exporting is itself recorded.

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 and incident history at insito.hyperping.app. The API, dashboard, SDK event delivery, and the MCP server are tracked as separate components, so you can tell at a glance which part of the platform an incident affects. Incidents are opened automatically when a monitor detects downtime.