Domandata
FeaturesProductPlansAboutHelp
Sign InCreate Free Account
Domandata
Follow us on LinkedInFollow us on YouTubeFollow us on XFollow us on Bluesky

Product

  • Features
  • Product
  • Plans
  • Changelog

Company

  • About
  • Cite
  • Help
  • Feedback

Account

  • Sign In
  • Create Account

Legal & Trust

  • Privacy
  • Terms
  • Security
  • Trust Center
  • Status
© 2026 Domandata LLC. All rights reserved.
Back to Help
Export

Pull Survey Responses With the API

Create API keys, pull a full labeled or recoded dataset (CSV, JSON, SPSS, or Stata) plus the codebook, paginate live updates, and recode data in R or Python.

What API Keys Let You Do

API keys give external scripts, dashboards, and data pipelines access to your survey responses without requiring a browser login. Instead of exporting a file, your tool sends a request to Domandata and gets back structured JSON it can process immediately.

Common uses include pulling responses into a live research dashboard, syncing data to a statistical analysis environment, triggering downstream workflows when new responses arrive, and archiving data to a lab server on a schedule.

If you're using Claude with the Domandata MCP connected, you don't need to copy these examples by hand — ask Claude to write the script for you. It has a dedicated get_api_export_guide tool that gives it the exact endpoint paths, parameters, and response shapes from this page, so it won't guess at the API.

EndpointUse it forRate limit
GET /api/v1/exportFull dataset in one call — CSV, JSON, SPSS, or Stata; labeled or raw recode values10/min
GET /api/v1/codebookQuestion text, variable labels, options, and recode values — no respondent data60/min
GET /api/v1/responsesCursor-paginated raw JSON — for polling new responses incrementally60/min

Export: Data Output

R: Skip the Boilerplate

If you're working in R, you don't need to write the httr/jsonlite calls shown later on this page yourself — source a small helper file we host instead:

source("https://www.domandata.net/r/domandata-helpers.R")

That gives you four functions: dd_codebook() and dd_data() wrap the requests below into one call each, and dd_recode() / dd_label() convert a data frame between display labels and codebook recode values for multiple-choice and dropdown columns — the step covered in "Recode Labels to Values" further down — without you writing the label-to-value mapping by hand.

source("https://www.domandata.net/r/domandata-helpers.R") 
# DOMANDATA_API_KEY must be set as an environment variable first
codebook <- dd_codebook(survey = "Customer Satisfaction Q3")
data <- dd_data(survey = "Customer Satisfaction Q3") # labels = TRUE by default
recoded <- dd_recode(data, codebook) # labels -> recode values
labeled <- dd_label(recoded, codebook) # recode values -> labels (inverse)

It's a single sourceable file, not an installed package — nothing to run install.packages() on beyond httr, jsonlite, and readr, which the calls above already depend on. The rest of this page shows what it's doing under the hood, and still applies directly if you're in Python or another language.

Export: Data Output

Create an API Key

  1. Step 1: Open Settings. From the top navigation, choose Settings.

    Step 1: Open Settings

  2. Step 2: Go to Advanced. Select the Advanced tab in Settings, then scroll to API Keys.

    Step 2: Go to Advanced

  3. Step 3: Start a new key. Click New API Key.

    Step 3: Start a new key

  4. Step 4: Name the key. Use the script or system name, such as R analysis pipeline or lab dashboard, so you can identify it later.

    Step 4: Name the key

  5. Step 5: Create and store it. Click Create, then copy the key immediately into your secrets manager or environment file.

    Step 5: Create and store it

  6. Step 6: Confirm ownership. Leave the Settings page only after you know which script owns the key and where the secret is stored.

    Step 6: Confirm ownership

Each key has an internal prefix shown in the list so you can identify it later. Keys start with dmdt_.

Make a Request

Open the survey you want to pull from and copy the survey UUID from its URL. Pass the key as a Bearer token in the Authorization header. Replace YOUR_KEY with the key you copied and SURVEY_ID with the UUID.

GET /api/v1/responses?survey_id=SURVEY_ID 
Authorization: Bearer YOUR_KEY

Example with curl:

curl -H "Authorization: Bearer dmdt_your_key_here" 
"https://www.domandata.net/api/v1/responses?survey_id=abc123"

The response is JSON:

{ 
"data": [
{
"id": "uuid",
"survey_id": "uuid",
"created_at": "2026-05-23T12:00:00Z",
"answers": { "question-id": "value" },
"metadata": { "submission_source": "published" }
}
],
"next_cursor": "2026-05-23T11:00:00Z"
}

Export: Data Output

Paginate Through Responses

Results are returned newest-first in pages of up to 50 by default (maximum 200). When there are more results, next_cursor contains a timestamp to pass as cursor on your next request.

# First page 
GET /api/v1/responses?survey_id=SURVEY_ID&limit=100
# Next page - pass the cursor from the previous response
GET /api/v1/responses?survey_id=SURVEY_ID&limit=100&cursor=2026-05-23T11:00:00Z

Keep requesting with the returned cursor until next_cursor is null, which means you have reached the end of the data.

Export: Data Output

Export Full Data as CSV

/api/v1/responses is built for polling — one page at a time. When you just want the whole dataset in one call (for example, to seed a local CSV in R and keep it updated), use /api/v1/export instead. It fetches every response server-side and returns it as a single CSV or JSON payload.

You can identify the survey by survey_id (its UUID) or by survey (its exact name, case-insensitive). Since survey names aren't required to be unique, an ambiguous name returns a 400 with a list of matching survey IDs to disambiguate with — pass survey_id going forward if that happens.

labels controls whether choice-type answers come back as their human-readable text (labels=true, the default) or their codebook recode values (labels=false) — the same distinction as the in-app CSV export's label/recode toggle.

curl -H "Authorization: Bearer dmdt_your_key_here" 
"https://www.domandata.net/api/v1/export?survey=Customer%20Satisfaction%20Q3&labels=true"
-o responses.csv

In R:

library(httr) 
library(readr)
key <- Sys.getenv("DOMANDATA_API_KEY")
resp <- GET(
"https://www.domandata.net/api/v1/export",
add_headers(Authorization = paste("Bearer", key)),
query = list(survey = "Customer Satisfaction Q3", format = "csv", labels = "true")
)
stop_for_status(resp)
data <- read_csv(content(resp, "text", encoding = "UTF-8"))

In Python:

import os, requests, pandas as pd 
from io import StringIO
key = os.environ["DOMANDATA_API_KEY"]
resp = requests.get(
"https://www.domandata.net/api/v1/export",
headers={"Authorization": f"Bearer {key}"},
params={"survey": "Customer Satisfaction Q3", "format": "csv", "labels": "true"},
)
resp.raise_for_status()
data = pd.read_csv(StringIO(resp.text))

Pass format=json instead of the default format=csv to get the same rows back as JSON (each with a quality_assessment block), if that's more convenient for your pipeline than parsing CSV.

Full CSV/JSON exports are capped at 20,000 responses per call (SPSS/Stata has a separate, lower cap — see below) — a survey with more responses than that returns 413, and you should fall back to paginating through /api/v1/responses instead. Because each call re-fetches the whole dataset, keep polling for live updates on the cursor-based /api/v1/responses endpoint (see above) — reserve /api/v1/export for the initial pull and occasional full re-syncs.

Export: Data Output

Export as SPSS, Stata, or R

Pass format=sav, format=dta, or format=rdata to /api/v1/export instead of csv/json to get a native SPSS, Stata, or R data file — the same file the app's Deploy tab Export card produces, now available to any script or scheduled job that has an API key, not just the browser.

curl -H "Authorization: Bearer dmdt_your_key_here" 
"https://www.domandata.net/api/v1/export?survey=Customer%20Satisfaction%20Q3&format=sav"
-o responses.sav

Every column comes back numeric-coded with its value labels attached — single-select multiple-choice and dropdown options, radio-style grid rows, single-category card sort cards, MaxDiff Best/Worst picks, conjoint's Selected Alternative column, and the survey-language column when a survey has more than one — the format's own built-in equivalent of labels=true, so labels and flat don't apply to sav/dta/rdata and are ignored if passed. Variable names are sanitized and de-duplicated to fit both formats' naming rules (letters, digits, underscores, 32 characters); the variable label is the question's actual wording, not the short variable name, so haven::read_dta()/read_sav() in R gives every column both a real label (attr(col, "label")) and its value labels with no separate codebook join needed.

If you'd rather not add haven/labelled as a dependency at all, use format=rdata instead: it's a real .RData file, loadable with plain load() (no package needed), which binds a data frame named responses. Every categorical column is already a genuine base-R factor — not a haven_labelled vector needing as_factor() — with its levels in the survey's authored option order, and every column's variable label is a plain attr(col, "label"). A blank/unanswered cell is real NA in every column, including free-text ones (CSV/SPSS/Stata instead keep those as a literal empty string, since those formats have no equivalent "missing, not blank" marker).

These three formats build the whole file in memory in one request rather than streaming it, so they're capped separately and lower than CSV/JSON: 15,000 responses per call, not 20,000. A survey past that returns 413 — use format=csv or format=json instead (the labels=false CSV export carries the same numeric coding, just as text rather than a binary file).

Export: Data Output

Get the Codebook

/api/v1/codebook returns the survey's data dictionary — no respondent data, just question text, variable labels, answer options, and recode values — so you can label columns and build factors without opening the survey in the app. It accepts the same survey_id or survey parameter as the other endpoints.

curl -H "Authorization: Bearer dmdt_your_key_here" 
"https://www.domandata.net/api/v1/codebook?survey_id=abc123"

Each entry in questions gives you question_id (the key under answers in /api/v1/responses and /api/v1/export), variable_label (short column name), title (the full question text), and — for choice-bearing types — an options (or rows/columns/items) array of { raw_value, display_label, recode_value } triples:

{ 
"survey_id": "abc123",
"survey_name": "Customer Satisfaction Q3",
"questions": [
{
"question_id": "q1",
"variable_label": "satisfaction",
"title": "How satisfied are you with our service?",
"type": "multiple_choice",
"options": [
{ "raw_value": "Very satisfied", "display_label": "Very satisfied", "recode_value": "1" },
{ "raw_value": "Neutral", "display_label": "Neutral", "recode_value": "2" },
{ "raw_value": "Very dissatisfied", "display_label": "Very dissatisfied", "recode_value": "3" }
]
}
]
}

In R, use it to build a lookup you can apply after pulling labels=false recode-value data from /api/v1/export:

library(httr) 
library(jsonlite)
key <- Sys.getenv("DOMANDATA_API_KEY")
codebook <- fromJSON(content(GET(
"https://www.domandata.net/api/v1/codebook",
add_headers(Authorization = paste("Bearer", key)),
query = list(survey_id = "abc123")
), "text", encoding = "UTF-8"), flatten = TRUE)
# variable_label -> title, for renaming columns after import
var_labels <- setNames(codebook$questions$title, codebook$questions$variable_label)

In Python:

import os, requests 
key = os.environ["DOMANDATA_API_KEY"]
codebook = requests.get(
"https://www.domandata.net/api/v1/codebook",
headers={"Authorization": f"Bearer {key}"},
params={"survey_id": "abc123"},
).json()
# variable_label -> title, for renaming columns after import
var_labels = {q["variable_label"]: q["title"] for q in codebook["questions"]}

Export: Data Output

Recode Labels to Values

"Recoding" means converting a choice-type answer between its human-readable text (display_label, e.g. "Very satisfied") and its codebook value (recode_value, e.g. "1"). There are two ways to get recoded data, depending on whether you already pulled it:

Starting fresh: pass labels=false to /api/v1/export (see "Export Full Data as CSV" above) and the API hands you already-recoded data directly — no client-side work.

Starting from data you already pulled with labels=true: build a display_label → recode_value map per question from /api/v1/codebook's options array, then apply it column by column. In R, dd_recode() from the helper file above does exactly this — and its inverse, dd_label(), converts the other direction:

source("https://www.domandata.net/r/domandata-helpers.R") 
codebook <- dd_codebook(survey = "Customer Satisfaction Q3")
data <- dd_data(survey = "Customer Satisfaction Q3") # labels = TRUE
recoded <- dd_recode(data, codebook)

It only touches multiple_choice and dropdown columns — those are the types whose CSV cell is a plain "label" or "label | label" string matching the codebook's options array 1:1. Other choice-bearing types (grid_matrix, constant_sum, drag_to_order, ...) store compound shapes (e.g. "Row: Column" pairs) that aren't a simple label swap, so those columns are left untouched rather than guessed at — for those, re-pull with labels=false instead.

In Python or another language, the same approach works without the helper: for each question in the codebook, build a dict from display_label to recode_value using its options, then .map()/replace that question's column.

Export: Data Output

API Best Practices

  • Never commit keys to source control. Store keys in environment variables or a secrets manager, not in code files. If a key appears in a git commit, delete it immediately and create a new one.
  • One key per system. Create a separate key for each script or integration. This makes it easy to revoke access for one system without disrupting others.
  • Name keys descriptively. A name like Python export script or lab Shiny dashboard makes it clear what to revoke if a key is compromised.
  • Use cursor-based pagination for large datasets. Fetching all responses in one large request is slower and more fragile. Page through results with cursor so a network failure only loses one page, not the full export.
  • Handle errors gracefully. A 401 means the key is invalid or expired. A 403 means the key does not have access to that survey. A 429 means you are sending requests too fast - add a delay between pages. Log error codes rather than silently dropping data.
  • Poll at a reasonable interval. If you are monitoring for new responses in real time, polling every 30-60 seconds is a reasonable cadence. Polling faster than once every few seconds provides little benefit and adds unnecessary load.
  • Store the cursor between runs. If your script runs on a schedule, save the last next_cursor value to disk or a database between runs. On the next run, start from that cursor to fetch only new responses rather than re-downloading everything.
  • Rotate keys periodically. For sensitive or long-running projects, create a new key, update your systems to use it, and delete the old key. Routine rotation limits exposure if a key was inadvertently logged or shared.

Export: Data Output

Delete a Key

Go to Settings, open Advanced, scroll to API Keys, and click the delete icon next to the key. Confirm when prompted. Any script using that key will immediately receive 401 errors. Create and distribute a new key before deleting an old one if the system needs continued access.

Export: Data Output

Related Help

  • Export Research Survey Responses
  • Survey Variable Names and Recodes
  • Pre-Analysis Plans and Survey Codebooks
  • Privacy and Anonymity in Research Surveys

More Analysis Workflow

Export survey data to Stata, R, or Python with the codebook still attached, then copy a methods-section citation for the software.

  • Export Survey Data to Stata, R, and Python — Export responses, condition assignments, randomized display order, metadata, and documentation for analysis in Stata, R, or Python.
  • Survey Codebook in R and Pre-Analysis Files — Build a survey codebook from variable names, recodes, and a pilot export before analysis in R or Stata.
  • How to Cite Domandata in Your Methods — Copy a ready-made methods sentence plus APA, Chicago, and BibTeX citations, including the software version and Zenodo DOI.
  • Survey Variable Names and Recodes — Create readable variable names, stable answer labels, and recodes before the data reach analysis.
  • Export Instruments, Codebooks, and Flow Diagrams — Download survey instruments, codebooks, Flow diagrams, security overviews, and replication files for review workflows.
  • Randomize Survey Questions and Answer Options — Randomize questions, answers, rows, columns, or sliders when order effects could shape responses, and keep randomization order in export.