BETA
Skip to content

Surveys API

Manage surveys, sections, and questions. All endpoints on this page are authenticated and scoped to the team that owns the API key.

Base URL: https://app.krafter.dev/api/v1

Authentication: Bearer token with surveys:read (read endpoints) or surveys:write (write endpoints). See API → Authentication.

For collecting responses, see Responses. For event delivery, see Webhooks. For aggregates, see Analytics.

The Survey object

FieldTypeDescription
idstringUUID
namestringDisplay name
slugstringURL-safe identifier — unique per team. Used in public response URLs (/api/v1/surveys/:slug/...) and embed config
modestringmanual (you author every question) or ai (the AI generates the next question per respondent)
statusstringdraft, active, or closed. Only active surveys accept responses
settingsobjectFree-form display settings (theme, button labels, redirect URL). Schema is owned by the embed widget
ai_configobjectAI mode configuration (goal, target_questions, tone). Empty {} for mode: "manual"
response_countintegerCached count of completed responses. Increments on complete_response
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp

GET /surveys/:id and POST /surveys/:id/duplicate additionally include sections and questions arrays (preloaded). List endpoints omit them.


List Surveys

GET /surveys

Required scope: surveys:read

Query parameters

ParamTypeDescription
pageinteger1-based page number. Default 1
per_pageintegerItems per page. Default 20, max 100
statusstringFilter by status (draft, active, closed)

Example Request

bash
curl "https://app.krafter.dev/api/v1/surveys?status=active&page=1" \
  -H "Authorization: Bearer kr_live_abc123def456"

Example Response

json
{
  "data": [
    {
      "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
      "name": "Product feedback",
      "slug": "product-feedback",
      "mode": "manual",
      "status": "active",
      "settings": {},
      "ai_config": {},
      "response_count": 142,
      "created_at": "2026-04-01T10:00:00Z",
      "updated_at": "2026-04-15T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 20,
    "total": 1,
    "total_pages": 1
  }
}

Create Survey

POST /surveys

Required scope: surveys:write

Request Body

FieldTypeRequiredDescription
namestringYesDisplay name
slugstringYesURL-safe identifier, unique per team
modestringNomanual (default) or ai
settingsobjectNoDisplay/widget settings
ai_configobjectNoRequired when mode: "ai" — at minimum {"goal": "..."}

The survey is created with status: "draft". Use POST /surveys/:id/activate to start collecting responses.

Example Request

bash
curl -X POST https://app.krafter.dev/api/v1/surveys \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Product feedback",
    "slug": "product-feedback",
    "mode": "manual"
  }'

Example Response

json
// 201 Created
{
  "data": {
    "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
    "name": "Product feedback",
    "slug": "product-feedback",
    "mode": "manual",
    "status": "draft",
    "settings": {},
    "ai_config": {},
    "response_count": 0,
    "created_at": "2026-05-10T10:00:00Z",
    "updated_at": "2026-05-10T10:00:00Z"
  }
}

Get Survey

GET /surveys/:id

Required scope: surveys:read

Returns the survey with sections and questions arrays preloaded.

Example Response

json
{
  "data": {
    "id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
    "name": "Product feedback",
    "slug": "product-feedback",
    "mode": "manual",
    "status": "active",
    "settings": {},
    "ai_config": {},
    "response_count": 142,
    "created_at": "2026-04-01T10:00:00Z",
    "updated_at": "2026-04-15T12:00:00Z",
    "sections": [
      {
        "id": "s1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
        "survey_id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
        "title": "About you",
        "description": null,
        "position": 1,
        "created_at": "2026-04-01T10:01:00Z"
      }
    ],
    "questions": [
      {
        "id": "q1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
        "survey_id": "b1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
        "section_id": "s1c2d3e4-5f6a-7b8c-9d0e-1f2a3b4c5d6e",
        "type": "text",
        "title": "What did you build with Krafter?",
        "description": null,
        "options": [],
        "settings": {},
        "position": 1,
        "branching": {},
        "version": 1,
        "created_at": "2026-04-01T10:02:00Z"
      }
    ]
  }
}

Update Survey

PATCH /surveys/:id

Required scope: surveys:write

Send only the fields you want to change. name, slug, mode, status, settings, and ai_config are accepted.

Slug changes break public links

Changing slug invalidates existing embed configurations and respondent invitation URLs (/s/:slug). Prefer renaming via name only.

Example Request

bash
curl -X PATCH https://app.krafter.dev/api/v1/surveys/SURVEY_ID \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"name": "Q2 product feedback"}'

Delete Survey

DELETE /surveys/:id

Required scope: surveys:write

Permanently deletes the survey and all associated sections, questions, responses, respondents, webhooks, and summaries.

204 No Content

Duplicate Survey

POST /surveys/:id/duplicate

Required scope: surveys:write

Creates a new draft survey with the same sections, questions, settings, and ai_config. Responses, respondents, and webhooks are not copied. The new survey gets a fresh id, slug (suffixed to avoid collision), and response_count: 0.

Returns the new survey with sections and questions preloaded.

201 Created

Activate Survey

POST /surveys/:id/activate

Required scope: surveys:write

Transitions status to active. The survey now accepts responses at POST /api/v1/surveys/:slug/responses and via the embed widget. No request body required.

bash
curl -X POST https://app.krafter.dev/api/v1/surveys/SURVEY_ID/activate \
  -H "Authorization: Bearer kr_live_abc123def456"
json
{ "data": { "id": "...", "status": "active", "...": "..." } }

Close Survey

POST /surveys/:id/close

Required scope: surveys:write

Transitions status to closed. New start_response requests are rejected. Existing in-progress responses can still complete. No request body required.

To reopen a closed survey, use POST /surveys/:id/activate.


List Templates

GET /surveys/templates

Required scope: surveys:read

Returns the catalog of built-in survey templates (NPS, CSAT, CES, Product Feedback, Onboarding, Employee Engagement). Templates are starting points for the dashboard's "New from template" flow — there is no public "instantiate template" endpoint via the API. Build a survey from scratch with the standard create + add-questions flow.

json
{
  "data": [
    { "id": "nps", "name": "Net Promoter Score", "description": "Single-question loyalty metric." },
    { "id": "csat", "name": "Customer Satisfaction", "description": "..." }
  ]
}

Sections

Sections group questions into logical units. Sections are optional — a survey can have questions with section_id: null.

The Section object

FieldTypeDescription
idstringUUID
survey_idstringOwning survey
titlestring | nullDisplay title
descriptionstring | nullOptional helper text shown above questions
positioninteger1-based ordering within the survey. Required
created_atstringISO 8601 timestamp

List Sections

GET /surveys/:survey_id/sections

Required scope: surveys:read

Returns sections ordered by position.

Create Section

POST /surveys/:survey_id/sections

Required scope: surveys:write

FieldTypeRequired
titlestringNo
descriptionstringNo
positionintegerYes
bash
curl -X POST https://app.krafter.dev/api/v1/surveys/$SURVEY_ID/sections \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"title": "About you", "position": 1}'
201 Created

Update Section

PATCH /surveys/:survey_id/sections/:section_id

Required scope: surveys:write

Accepts title, description, position. Send only the fields you want to change.

Delete Section

DELETE /surveys/:survey_id/sections/:section_id

Required scope: surveys:write

Questions belonging to the section are not deleted — their section_id is set to null. Reattach them to another section or leave them unsectioned.

204 No Content

Questions

The Question object

FieldTypeDescription
idstringUUID
survey_idstringOwning survey
section_idstring | nullOwning section, or null for top-level questions
typestringOne of the 12 question types — see below
titlestringThe question text shown to respondents. Required
descriptionstring | nullOptional helper text shown beneath the title
optionsstring[]Choice labels for choice, multi_choice, ranking. Empty for free-text types
settingsobjectType-specific config (e.g. {"min": 0, "max": 10, "labels": {...}} for scale)
positioninteger1-based ordering across the survey. Required
branchingobjectConditional logic — see Branching
versionintegerIncremented on edits to support response migration
created_atstringISO 8601 timestamp

Question types

TypeAnswer shapeNotes
textstringSingle-line or paragraph (controlled by settings.multiline)
emailstringValidated as RFC-5322
choicestringSingle selection from options[]
multi_choicestring[]Multiple selection from options[]
ratinginteger1-5 star rating
npsinteger0-10
scaleintegerNumeric scale; bounds in settings.min / settings.max
sliderintegerContinuous slider; bounds in settings.min / settings.max
matrixobject{row: column} map; rows in settings.rows, columns in settings.columns
rankingstring[]Ordered list of options[] values
yes_nobooleantrue / false
datestringISO 8601 date (YYYY-MM-DD)

Branching

Each question may carry a branching object that tells the engine which question to ask next based on the answer:

json
{
  "rules": [
    { "if": { "equals": "Yes" }, "then": { "go_to_question_id": "q-9c8b..." } },
    { "if": { "equals": "No" },  "then": { "skip_to_end": true } }
  ],
  "default": { "go_to_next": true }
}

Branching is enforced server-side when the respondent submits each answer. AI-mode surveys ignore branching — the AI engine picks the next question dynamically.

List Questions

GET /surveys/:survey_id/questions

Required scope: surveys:read

Returns questions ordered by position.

Create Question

POST /surveys/:survey_id/questions

Required scope: surveys:write

FieldTypeRequired
typestringYes
titlestringYes
positionintegerYes
section_idstringNo
descriptionstringNo
optionsstring[]Required for choice, multi_choice, ranking
settingsobjectType-specific
branchingobjectNo
bash
curl -X POST https://app.krafter.dev/api/v1/surveys/$SURVEY_ID/questions \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "choice",
    "title": "How did you hear about us?",
    "options": ["Search", "Social", "Friend", "Other"],
    "position": 2
  }'
201 Created

Update Question

PATCH /surveys/:survey_id/questions/:question_id

Required scope: surveys:write

Send only the fields you want to change. Editing a question on an active survey increments version so existing responses remain valid against the prior shape.

Delete Question

DELETE /surveys/:survey_id/questions/:question_id

Required scope: surveys:write

Existing answers for this question are retained inside the response's answers map but are no longer rendered.

204 No Content

Reorder Questions

POST /surveys/:survey_id/questions/reorder

Required scope: surveys:write

Atomically rewrites every question's position according to the supplied id order. Pass all question ids — partial lists return 400.

bash
curl -X POST https://app.krafter.dev/api/v1/surveys/$SURVEY_ID/questions/reorder \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "question_ids": [
      "q1c2d3e4-...",
      "q2c2d3e4-...",
      "q3c2d3e4-..."
    ]
  }'
json
{ "ok": true }

Errors

Standard platform errors apply — see API → Errors. Surveys-specific notes:

  • 404 Not Found is returned for any survey, section, or question id that doesn't belong to your team — not 403. The API never confirms the existence of resources outside your team.
  • Activating a survey with zero questions returns 422 Unprocessable Entity.
  • Slug collisions return 422 with errors.slug: ["has already been taken"].

Built by Krafter Studio