/v1/text_responses endpoint returns the free-text answers to open questions, each with the topics Honestly’s analysis assigned to it and the sentiment it expresses about each of those topics. That lets you reuse the platform’s own categorization instead of rebuilding it, or reconcile it against your own clustering.
Each entry contains the answer text and its topics. Author, attribute values, submission time and response ID are deliberately absent — every one of those narrows down who wrote a given sentence, and free-text answers are the most identifying content a survey collects. Topics and sentiment are derived from the text you already receive, so they add no identifying information beyond it.
Answers are selected by survey or by survey cycle — never by a date range. The survey cycle is the finest slice you can address. Why there is no date range explains what that protects against and what it costs you.
Endpoint
Query Parameters
string
Comma-separated survey IDs, from
/v1/surveys. Returns the answers from every cycle of these surveys. Omit to cover every survey in the account.string
Comma-separated survey cycle UUIDs, from
/v1/survey_cycles. Narrows the export to these cycles rather than a survey’s full history. Omit to include every cycle of the surveys in scope.There is no date-range parameter, and no way to address part of a cycle. See Why there is no date range.string
Comma-separated question IDs. Only open-text questions return answers. IDs come from
/v1/questions. Omit to return every open-text question in scope.string
Comma-separated attribute option IDs from
/v1/attributes/{attribute_id}/options. Restricts answers to the people matching that cohort.All options must belong to the same attribute, and that attribute is fixed per account in the Honestly account settings — this endpoint filters by one attribute only. See Filtering by attribute.integer
Maximum number of answers to return per page. Accepts values from
0 to 1000. Defaults to 100.integer
Number of answers to skip before returning results. Defaults to
0.Example Request
Choosing a scope
Both scope parameters are optional, and they narrow in sequence:
Answers from everything in scope come back as one pooled list. There are no per-survey or per-cycle sections, and nothing in the payload says which cycle an answer came from — see Results are pooled, never labelled.
Example Response
Response Fields
integer
Total number of answers matching the filter, for pagination.
0 when the cohort is below the text anonymity threshold.array
Why there is no date range
This endpoint has noperiod_from / period_to. That is a deliberate constraint, not an omission, and it is worth understanding before you design your export around it.
A free date range would reveal when each answer was written
No timestamp appears in the payload, and the ordering is non-temporal — but a date range you can narrow at will puts the timestamp back. Requesting a window, then requesting a slightly shorter one, and comparing the two lists tells you which answers fall in the difference. Repeat and the submission date of any individual comment can be narrowed to a single day. That matters because timing is one of the strongest ways to identify an author. Knowing that a particular comment was written on a particular afternoon, combined with who was working, on leave or newly invited that day, can be enough to name the person — from an endpoint that returns nothing but text. A minimum window length does not fix this. Two windows of the same permitted length, offset by a day, are both legal and still differ by a single day of answers. Any scheme where you choose the boundaries has this property, however large the minimum.A fixed unit closes it
The cycle is the smallest thing you can address, so every request — whether you scoped it by survey, by cycle or not at all — is built from the same indivisible blocks. Comparing the results of two requests can therefore only ever isolate a whole cycle, never a sliver of one. That is why widening the scope is safe while slicing it finer is not.survey_ids and an unscoped request both return unions of whole cycles, and a union is only ever larger than its parts. Every cycle has already been checked against the free-text anonymity threshold before any of its answers are released, and the cycle is the unit that threshold is configured on — so the window you request and the window Honestly protects are the same window.
What this costs you
The finest slice available is one cycle. For a quarterly survey that means quarterly comment exports: you cannot request only March out of a January–March cycle, and you cannot align free text to an arbitrary reporting window the way you can with/v1/scores. For a continuously running survey with one long-lived cycle, it is all or nothing.
Scores are unaffected and keep their free date range. Narrowing a period on /v1/scores shrinks the cohort into the anonymity threshold, so the guard engages exactly when a slice becomes too small — an aggregate behaves differently from a list of individual comments.
Results are pooled, never labelled
Everything in scope comes back as one list rather than splitting into labelled sections, however you scoped it. Pooling only ever makes a cohort larger, and a per-cycle or per-survey breakdown would hand back exactly the timing information that dropping the date range is meant to withhold. This is also why the payload carries nosurvey_id or cycle_id on the individual answer. If you need comments attributed to a particular cycle, request that cycle on its own and label the results yourself.
If cycle granularity is too coarse for your reporting, contact service@honestly.com rather than working around it — the answer will be a coarser fixed unit such as calendar months, not a return of the date range.
Topics and sentiment
Sentiment belongs to a topic, not to an answer
There is no single sentiment for an answer. Sentiment is scored per (answer, topic) pair, so an answer assigned three topics carries three independent scores. This matters because real comments are mixed."Too many meetings, but my manager is supportive." is negative about Workload and positive about Management in the same sentence. Collapsing that to one number — by taking the first topic’s score, or averaging the three — throws away the part that makes the comment worth reading.
Treat the pair as your unit of analysis. If you are counting sentiment, count pairs, and say so in your reporting: an answer touching three topics contributes three data points, so the pair total will exceed total_count.
An empty topics array is not “neutral”
topics: [] means no topic was assigned — the answer was too short or too vague to categorize, or analysis has not reached it yet. A null sentiment on an assigned topic likewise means “not scored”, not “neutral”. neutral is a positive finding: the analysis read the answer and judged it neither favourable nor critical about that topic.
Keep the three cases apart in your pipeline. Folding “unscored” into “neutral” quietly inflates the neutral bucket, and it inflates it most on exactly the recent data where analysis is still catching up.
Group by name, and handle renames
A topic is identified by itsname. Names are unique within your account, so grouping answers by the exact string is the intended way to aggregate — no ID lookup and no second call.
The one thing to plan for is that an admin can rename a topic in the Honestly interface. A rename applies to every answer ever assigned to that topic, including answers you exported before the change, so a long-running export will see the old name in your stored data and the new name in fresh results, with no signal connecting the two.
If you retain data across months, keep your own alias table mapping old names to current ones, and reconcile when an unfamiliar topic name appears alongside a familiar one disappearing.
Match on the string as returned. Names are unique per account case-insensitively, so you will never receive two topics that differ only in capitalization — but do not normalize punctuation or whitespace away, since Strategy & Direction and Strategy and Direction are two distinct topics that can both exist.
Topics can be corrected after the fact
Topic assignments are not frozen once written. An account admin can reassign a topic on an answer in the Honestly interface, and the sentiment for that pair is re-scored when they do. Automated re-categorization can also revise older answers. So an answer you exported last month may come back with different topics or a different sentiment today, while itstext is unchanged. If you cache, re-fetch periodically rather than treating a first read as final.
Filtering by attribute
Pass attribute option IDs to read only the answers written by a particular cohort — for example only the comments from Engineering, or from Engineering and Sales together.One attribute only, chosen in account settings
This is the one place the attribute filter behaves differently from/v1/scores, and it is the constraint most likely to break an integration ported from that endpoint.
- A single attribute is filterable. An account admin picks which one in the Honestly account settings. It is a per-account choice, not a per-request one — you cannot select the attribute through the API, and there is no parameter for it.
- Multiple options of that attribute are allowed, and they are OR-ed.
31,32means Engineering or Sales. - Attributes cannot be combined. There is no AND here. Passing options of two different attributes returns
400, as does passing an option of any attribute other than the configured one — soEngineering in Berlinis not expressible on this endpoint the way it is on/v1/scores. - When no attribute is configured,
attribute_option_idsis rejected with400and free text can only be read unfiltered.
/v1/attributes lists everything released for API use across the API — it does not single out the one this endpoint accepts, so discovering it by trial means reading 400s.
The limit exists because free text is the most identifying content a survey collects. Each attribute you can intersect multiplies the number of distinct cohorts you can address, and a cohort narrow enough to name —
Engineering, Berlin, joined this year — reads a handful of comments back to their authors. One attribute keeps the addressable cohorts coarse; scores can afford to be finer because an aggregate reveals far less than the sentences behind it.Six things to build into your pipeline
The order is not chronological
Answers come back in a stable but non-temporal order. Returning them newest first would leak timing through the ordering even though no timestamp appears in the payload, and timing is one of the strongest ways to identify an author. The order is stable across calls, so pagination is safe — an answer will not move between pages while you are reading them.Personal information masking is optional
PII masking is switched off by default. When an account admin turns it on, free-text answers pass through an automated step that masks personal information — names, email addresses, phone numbers, postal addresses and identification numbers — before they can leave the platform. Which of those categories are masked is part of the same setting. With masking off,text is returned verbatim, exactly as the respondent typed it. Anything they wrote about themselves or a colleague comes through unaltered, so treat the field as free-form personal data in your own storage and access controls. The anonymity threshold still applies either way; it governs whether a cohort’s comments are released, not what the released sentences contain.
Two things are outside the setting’s reach: responses that were imported rather than submitted through Honestly are never masked, and masking is unavailable to accounts without the data-masking entitlement even if the toggle is visible.
With masking on, answers appear only once it has completed
The masking step runs asynchronously, so with masking on a very recent answer may be missing from one call and present in the next. Re-fetch a cycle rather than assuming a page is final, and expecttotal_count to grow while a cycle is still open or has only recently closed. With masking off there is no such step and answers are exportable as soon as they are submitted.
Topics and sentiment lag behind the text
Categorization and sentiment scoring run as their own asynchronous step, after masking where masking applies. An answer can therefore be returned with itstext populated and topics still empty, and a topic can appear with sentiment: null before it is scored.
This is the normal state of recently submitted answers, not an error. If your pipeline needs complete topic coverage, either lag your export window behind the present or re-fetch recent periods until the empty-topics share settles.
Free text has its own anonymity threshold
Open text is governed by a text-specific threshold, configured per survey and evaluated per cycle, separate from the score threshold. When the selected cohort is below it, you receive an empty list andtotal_count: 0 — never a partial list. A partial list of comments is exactly what a small cohort must not expose.
The threshold is applied per cycle, not to your request as a whole. Any cycle in scope that falls below it contributes nothing, and the response does not say which ones were excluded — naming them would report that fewer than the threshold number of people answered in a specific window, a headcount fact about a named period that this endpoint withholds everywhere else.
This is worth keeping in mind when you scope broadly. A survey_ids request over a survey’s whole history, or an unscoped one, silently omits its thin cycles while the healthy ones come through, so total_count is not the number of comments the survey ever collected. Suppression here is per cycle and it does not announce itself.
Always paginate
Individual answers can be long.limit and offset work as they do elsewhere in the API, and a default page size is enforced. See Pagination.
Topics and sentiment are returned per answer, but they are not filterable — there is no
topics or sentiment query parameter. Filter client-side after export. If you need server-side filtering, or topic and sentiment totals you would otherwise compute yourself, contact service@honestly.com.