Fast Insight API

Create studies, start recruitment, and retrieve results programmatically.

On this page

Programmatic access to study creation, launch, and results. Base path /api/public/v1. Versioned and additive-only: fields are added, never removed or repurposed within v1.

Authentication

Send your key in the x-api-key header:

curl https://fastinsight.ai/api/public/v1/studies \
  -H "x-api-key: fi_live_..."

Keys are user-scoped. A key acts as the person who created it and can reach exactly what that person can reach in the web app, and no more. This is why there is no "organisation key": project edit rights in Fast Insight belong to the owner and accepted collaborators (who may be in another organisation), not to everyone in an org, and a key that ignored that would grant access the UI denies.

Practical consequence: if the person who minted a key leaves, revoke and re-mint under someone who remains. Their keys are revoked automatically when their account is removed.

Plaintext is shown once at creation and never again. We store only a SHA-256 hash plus a short prefix, so a lost key must be revoked and replaced.

Scopes

ScopeGrants
studies:readList and read studies
studies:writeCreate and edit drafts
responses:readRead responses and transcripts
reports:readRead the synthesis report
launch:byopLaunch a bring-your-own-participants study (no spend)
launch:panelLaunch panel recruitment (spends credits)

studies:write deliberately does not imply launch, and panel launch needs launch:panel on top of launch:byop. An automation can therefore build a study without being able to spend money.

Errors

{
  "error": "Study not found.",
  "code": "not_found",
  "requestId": "0f9c...",
  "issues": [{ "path": "numberOfResponses", "message": "Expected number" }]
}

Branch on code, not on error (prose may change). Quote requestId in support requests. issues appears only on schema failures.

Auth failures always return 401 invalid_api_key. Missing, malformed, unknown, and revoked keys are indistinguishable by design. Anything you cannot reach returns 404 not_found rather than 403, so the API never confirms that someone else's study exists.

Rate limits

Per key, per minute: 120 reads, 30 writes, 6 expensive calls (launch, targeting suggestion). Exceeding a bucket returns 429 with Retry-After.

Idempotency

Send Idempotency-Key on launch. Retrying with the same key replays the original response instead of launching twice:

curl -X POST .../studies/{id}/launch \
  -H "x-api-key: fi_live_..." \
  -H "Idempotency-Key: 6f1c...".

The key is bound to the caller, endpoint, target study, and request body. Reusing one key with a different body returns 409 idempotency_key_reused rather than silently replaying. Records expire after 24h.

One deliberate subtlety: if recruitment starts but the study record fails to finalise, the key is kept and the error replays. That path already spent money, so a retry must not re-enter the launch. Contact support to reconcile rather than retrying.

Endpoints

GET /studies

Cursor-paginated. limit (1–100, default 25), cursor, status (DRAFT|ACTIVE|ARCHIVED).

{ "data": [ { "id": "...", "title": "...", "status": "ACTIVE" } ],
  "nextCursor": "eyJ..." }

Pass nextCursor back as cursor; null means the last page.

POST /studies/{id}/launch

Moves a DRAFT to ACTIVE and starts recruitment. Body is optional; every field overrides the stored value for this launch only.

{ "numberOfResponses": 30, "targetCountry": "JP", "interviewLanguage": "ja" }

Returns recruit links as absolute URLs:

{ "id": "...", "status": "ACTIVE", "recruitmentMode": "PANEL",
  "recruitLinks": [ { "groupId": "...", "label": "グループ1", "url": "https://..." } ] }

Notable failures: 402 insufficient_credits (with balance and shortfall in details), 400 below_min_responses, 400 feasibility_red (not enough panel supply), 409 study_busy (a launch is already running), 400 targeting_incomplete.

Launches run through exactly the same code path as the web app, including the guard that prevents a study from being launched twice.

POST /studies

Create a DRAFT. title, purpose, and customerDefinition (at least one trigger or exclusion condition) are required; pass guide to get a launch-ready draft in one call. Accepts Idempotency-Key.

The body is strict: an unknown field is a 400 naming the field, not a silent ignore. modality is VOICE or VIDEO; recruitmentMode defaults to PANEL. Response count belongs to launch, not create.

A guide item looks like this. id must be a UUID (the editor generates one per row), and followUpDepth is an enum, not a number:

{
  "id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
  "part": 1,
  "order": 0,
  "question": "最近の買い物で、迷ってからやめた経験を教えてください。",
  "followUpDepth": "1",
  "followUpGuideline": ""
}

followUpDepth is one of none, if_short, 1, 2_3. none and if_short must carry an empty followUpGuideline: the server strips it either way, so the guide you read back may differ from the one you sent.

GET / PATCH /studies/{id}

Read one study, or edit it. Edits are DRAFT-only: the API deliberately will not mutate a live study even though the UI permits some post-launch edits.

POST /studies/{id}/targeting/suggest

Panel recruitment needs cintProfileMappings: demographic conditions expressed as our panel provider's internal question ids. You cannot write those by hand, which is why this endpoint exists. Send plain demographics, get back a valid targeting object; apply: true saves it.

If the study's target-respondent conditions are too vague to map, this returns 502 feasibility_unavailable. Make them more specific and retry.

GET /studies/{id}/responses and /responses/{responseId}

Cursor-paginated. The list uses the researcher-visible filter (screened-out, quality-filtered, and preview interviews excluded); note this is looser than the report corpus, which is completed-only, so completedCount on the study and the length of this list are legitimately different numbers.

Transcript turns state their language explicitly rather than making you infer it:

{ "contentJa": "…", "originalText": "…", "sourceLanguage": "ko" }

originalText is null for Japanese interviews. A GDPR-erased response returns anonymized: true with no turns.

GET /studies/{id}/report

?format=json (default) or ?format=markdown. Markdown is a deterministic render with sourced participant quotations, never an LLM summary, so it cannot drift from what the researcher sees. 404 report_not_ready until enough interviews complete.

Frequently asked questions

Does Fast Insight have a public API?
Yes. The public REST API at https://fastinsight.ai/api/public/v1 covers creating a study, starting recruitment, reading responses and transcripts, and pulling the synthesis report. Authentication is an x-api-key header, and permissions are per scope.
Where do I get an API key?
Under Account settings, Developer section. Keys are user-scoped: a key reaches exactly what its creator can reach in the web app. The plaintext is shown once at creation and never again.
Does launching recruitment through the API cost money?
Panel recruitment does. Starting it therefore needs the launch:panel scope AND a separate human approval in the browser, every time. Bring-your-own-participants studies cost nothing.