Skip to content

Surveys API

Read access to Creative Survey / Ask One answer data held in Databricks. See API conventions for the shared rules.

Base path: /api/v1/surveys

Method Path Summary
GET / List surveys
GET /{surveyId} Get survey detail
GET /{surveyId}/questions List survey questions
GET /{surveyId}/preview Preview survey data
GET /{surveyId}/panels List respondents
GET /{surveyId}/panels/{panelId} Get respondent detail
GET /{surveyId}/respondents List respondents (legacy)

Every endpoint requires Authorization: Bearer <access_token>.

No role restriction

This is the one router that does not apply requireRole(). Now that it is read-only the exposure is limited, but a viewer can still read the answers of every survey.

Pagination is offset / limit

List endpoints take offset / limit rather than page, and return { items, total, limit, offset } inside data. The shared pagination field is not present.

How the Data Flows

graph LR
  CS[(Creative Survey)]
  S3[(S3<br/>answers.csv.gz / panels.csv.gz)]
  Pipe[cs_data_pipeline_s3_delivery<br/>daily 05:00:45 JST]
  DM[(cs.cs_dm.dm_answers_&lt;survey_id&gt;<br/>answers joined with panels)]
  DWH[(cs.cs_dwh)]
  Meta[databricks-meta-sync]
  Cache[(databricks_surveys)]
  API[Surveys API]

  CS --> S3 --> Pipe --> DM
  Pipe --> DWH
  DWH --> Meta --> Cache
  DM --> API
  Cache --> API

A pipeline on the Databricks side ingests the S3 files and materialises one table per survey, dm_answers_<survey_id>, with answers already joined onto panels. The API reads that directly.

Source Used by
databricks_surveys (Postgres) List and detail. The aggregation takes 15 seconds, so it is cached daily
cs.cs_dm.dm_answers_<survey_id> Questions, preview, respondent endpoints

Roughly one second of fixed overhead per query

A round trip through the Databricks SQL Statement Execution API costs about a second. The per-survey tables are therefore not touched to render a list; metadata comes from the Postgres cache instead.

Unknown survey IDs return 404

Per-survey endpoints first check that a row exists in databricks_surveys, so an unknown ID does not surface as a Databricks "table does not exist" error (502).


List Surveys

Lists from databricks_surveys (the metadata cache).

URI

GET /api/v1/surveys

Query Parameters

Field Type Required Default Rules Description
search string - - - Partial match on survey name
sort string - -latestResponseAt - Prefix with - for descending
limit integer - 20 1–100 Items per page
offset integer - 0 ≥ 0 Starting position

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveys": [
      {
        "surveyId": "234567",
        "name": "Spring 2026 User Survey",
        "lastModified": "2026-01-31T05:00:00Z",
        "fileCount": 5,
        "respondentCount": 1234
      }
    ],
    "total": 12,
    "limit": 20,
    "offset": 0
  },
  "path": "/api/v1/surveys",
  "method": "GET"
}

Errors

Description Status code Status name
Malformed query 400 Bad Request
Token missing or invalid 401 Unauthorized
Databricks not configured (DATABRICKS_NOT_CONFIGURED) 503 Service Unavailable

Get Survey Detail

URI

GET /api/v1/surveys/{surveyId}

Path Parameters

Field Rules
surveyId Required, at least 1 character

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveyId": "234567",
    "surveyName": "Spring 2026 User Survey",
    "collectorName": "Main collector",
    "statistics": {
      "totalRespondents": 1234,
      "completedRespondents": 1100,
      "questionCount": 25,
      "earliestResponseAt": "2026-01-15T10:00:00+09:00",
      "latestResponseAt": "2026-01-30T18:30:00+09:00"
    },
    "syncedAt": "2026-01-31T05:00:00+09:00"
  },
  "path": "/api/v1/surveys/234567",
  "method": "GET"
}

The statistics are a snapshot from sync time.

Errors

Description Status code Status name
Token missing or invalid 401 Unauthorized
Survey does not exist 404 Not Found

List Survey Questions

Used to build segment definitions and import mapping choices.

URI

GET /api/v1/surveys/{surveyId}/questions

Query Parameters

Field Type Required Default Rules Description
search string - - - Partial match on question text
limit integer - 50 1–200 Items per page
offset integer - 0 ≥ 0 Starting position
includeOptions boolean - false - Whether to include options

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveyId": "234567",
    "questions": [
      {
        "questionId": "q_1",
        "questionText": "How satisfied are you with our service?",
        "questionType": "single_select",
        "options": ["Very satisfied", "Satisfied", "Neutral", "Dissatisfied"]
      }
    ],
    "total": 25,
    "limit": 50,
    "offset": 0
  },
  "path": "/api/v1/surveys/234567/questions",
  "method": "GET"
}
questionType Meaning
single_select Single choice
multi_select Multiple choice
text Free text
number Numeric
date Date
other Other

options is present only when includeOptions=true.

How questions are identified

The mart has no question ID — questions are identified by the question text itself. Import mappings also specify question text in sourceField. Matrix questions use the composite key question text|||answer item.

Errors

Description Status code Status name
Malformed query 400 Bad Request
Token missing or invalid 401 Unauthorized
Survey does not exist 404 Not Found

Preview Survey Data

Returns the question list and a sample of respondents together.

URI

GET /api/v1/surveys/{surveyId}/preview

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveyId": "234567",
    "questions": [
      { "questionId": "q_1", "questionText": "How satisfied are you with our service?", "questionType": "single_select" }
    ],
    "totalRespondents": 1234,
    "sampleRespondents": [
      {
        "respondentId": "119466675",
        "answers": { "q_1": "Very satisfied", "q_2": "Easy to use" },
        "completedAt": "2026-01-28T14:30:00Z",
        "metadata": {}
      }
    ]
  },
  "path": "/api/v1/surveys/234567/preview",
  "method": "GET"
}

Errors

Description Status code Status name
Token missing or invalid 401 Unauthorized
Survey does not exist 404 Not Found

List Respondents

Lists respondents by deduplicating the answer rows of the per-survey table.

URI

GET /api/v1/surveys/{surveyId}/panels

Query Parameters

Field Type Required Default Rules Description
isCompleted boolean - - - Filter by completion
search string - - - Search by panel ID and similar
sort string - -completedAt - Prefix with - for descending
limit integer - 20 1–100 Items per page
offset integer - 0 ≥ 0 Starting position

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "panels": [
      {
        "panelId": "119466675",
        "isCompleted": true,
        "customKey": "user_abc123",
        "completedAt": "2026-01-28T14:30:00+09:00",
        "deviceInfo": {
          "platform": "Windows",
          "browser": "Chrome",
          "browserVersion": "120.0",
          "os": "Windows 11",
          "resolution": "1920x1080",
          "isMobile": false,
          "ipAddress": "192.168.1.xxx"
        },
        "createdAt": "2026-01-28T14:00:00+09:00"
      }
    ],
    "total": 1100,
    "limit": 20,
    "offset": 0
  },
  "path": "/api/v1/surveys/234567/panels",
  "method": "GET"
}

Errors

Description Status code Status name
Malformed query 400 Bad Request
Token missing or invalid 401 Unauthorized
Survey does not exist 404 Not Found

Get Respondent Detail

Returns one respondent's attributes and all of their answers.

URI

GET /api/v1/surveys/{surveyId}/panels/{panelId}

Path Parameters

Field Rules
surveyId Required, at least 1 character
panelId Required, at least 1 character (respondent ID)

Query Parameters

Field Type Required Default Description
includeUnanswered boolean - false Whether to include unanswered questions

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "panel": {
      "panelId": "119466675",
      "isCompleted": true,
      "customKey": "user_abc123",
      "urlParameter": "source=email&campaign=spring2026",
      "completedAt": "2026-01-28T14:30:00+09:00",
      "deviceInfo": { "platform": "Windows", "browser": "Chrome", "browserVersion": "120.0", "os": "Windows 11", "resolution": "1920x1080", "isMobile": false, "ipAddress": "192.168.1.xxx" },
      "createdAt": "2026-01-28T14:00:00+09:00",
      "updatedAt": "2026-01-28T14:30:00+09:00"
    },
    "answers": [
      {
        "questionSentence": "Please enter your name",
        "answerType": "Free text",
        "answerItemSentence": null,
        "subItemSentence": null,
        "value": "Taro Yamada",
        "isAnswered": true
      }
    ],
    "answersSummary": {
      "totalQuestions": 25,
      "answeredQuestions": 23,
      "unansweredQuestions": 2
    }
  },
  "path": "/api/v1/surveys/234567/panels/119466675",
  "method": "GET"
}

answers mirrors the vertical layout of the mart (one row per question × option). A single question spans several rows, so the UI groups them by question.

Errors

Description Status code Status name
Token missing or invalid 401 Unauthorized
Survey or respondent does not exist 404 Not Found

List Respondents (Legacy)

Deprecated

Superseded by the /panels endpoints. Do not use in new code.

URI

GET /api/v1/surveys/{surveyId}/respondents

Query Parameters

Field Type Required Rules
limit integer - 1–1000
offset integer - ≥ 0
onlyCompleted boolean - -

Response (200 OK)

{
  "success": true,
  "status": "success",
  "statusCode": 200,
  "data": {
    "surveyId": "234567",
    "respondents": [
      { "respondentId": "119466675", "answers": { "q_1": "Very satisfied" }, "completedAt": "2026-01-28T14:30:00Z" }
    ],
    "total": 1100,
    "limit": 100,
    "offset": 0
  },
  "path": "/api/v1/surveys/234567/respondents",
  "method": "GET"
}

Unlike /panels/{panelId}, answers come back pivoted into an answers object.