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
| Scope | Grants |
|---|---|
studies:read | List and read studies |
studies:write | Create and edit drafts |
responses:read | Read responses and transcripts |
reports:read | Read the synthesis report |
launch:byop | Launch a bring-your-own-participants study (no spend) |
launch:panel | Launch 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.