## List **get** `/v1/feedback-records` Lists feedback records for a required tenant_id with optional additional filters and pagination ### Query Parameters - `tenant_id: string` Tenant ID (required for isolation). NULL bytes not allowed. - `created_since: optional string` Filter by created_at >= created_since (ISO 8601, inclusive). created_at is when Hub stored the record; collected_at (see `since`) is when the feedback was given. They diverge on a historical re-import, so this is the parameter for "what did this import bring in". Must be between 1970-01-01 and 2080-12-31. - `created_until: optional string` Filter by created_at <= created_until (ISO 8601, inclusive). See created_since for how created_at differs from collected_at. Must be between 1970-01-01 and 2080-12-31. - `cursor: optional string` Omit for the first page. For the next page, use the exact value from the previous response's next_cursor. Opaque (base64-encoded); keyset pagination. A cursor is a position within one specific ordering. Presenting it with a different sort or order returns 400 — restart pagination without a cursor instead. A cursor issued before sort control existed records no ordering; it is treated as a position in the default collected_at/desc listing and is accepted only there. - `emotions: optional array of "joy" or "anger" or "sadness" or 3 more` Filter by emotion label. Repeat the parameter to match ANY of the listed emotions: a record tagged {joy} and a record tagged {joy, anger} both match `?emotions=joy&emotions=anger`. There is no "must carry all of them" form. An empty value is ignored rather than rejected, so `?emotions=` is the same as omitting the filter. - `"joy"` - `"anger"` - `"sadness"` - `"fear"` - `"surprise"` - `"disgust"` - `field_group_id: optional array of string` Filter by field group ID (for ranking/matrix questions). Repeat the parameter to match any of several groups; the values are OR-ed. NULL bytes not allowed. - `field_id: optional array of string` Filter by field ID — every answer to one question. Repeat the parameter to match any of several fields; the values are OR-ed. NULL bytes not allowed. - `field_type: optional array of "text" or "categorical" or "nps" or 6 more` Filter by field type. Repeat the parameter to match any of several types; the values are OR-ed. An empty value is ignored rather than rejected, so `?field_type=` is the same as omitting the filter. - `"text"` - `"categorical"` - `"nps"` - `"csat"` - `"ces"` - `"rating"` - `"number"` - `"boolean"` - `"date"` - `has_emotions: optional boolean` Filter on whether the record carries emotion labels: true selects records where emotions IS NOT NULL, false selects those where it IS NULL. Note that false covers both "not yet classified" and "classified, no emotion detected" — the two are indistinguishable here. Omit for no constraint. - `has_sentiment: optional boolean` Filter on whether sentiment enrichment has produced a label: true selects records where sentiment IS NOT NULL, false selects those where it IS NULL. Omit for no constraint. - `has_translation: optional boolean` Filter on whether the record has been translated: true selects records where translation_lang_key IS NOT NULL, false selects those where it IS NULL. Omit for no constraint. - `language: optional array of string` Filter by the language the feedback was given in. Repeat the parameter to match any of several languages; the values are OR-ed. NULL bytes not allowed. - `limit: optional number` Number of results to return (max 1000) - `order: optional "asc" or "desc"` Sort direction. Defaults to desc. Rows tied on the sort column are ordered by id ascending. - `"asc"` - `"desc"` - `sentiment: optional array of "very_negative" or "negative" or "neutral" or 3 more` Filter by sentiment label. Repeat the parameter to match any of several labels (`?sentiment=negative&sentiment=very_negative`); the values are OR-ed. An empty value is ignored rather than rejected, so `?sentiment=` is the same as omitting the filter. Records that have not been enriched carry no sentiment and are therefore never matched — use has_sentiment=false to find them. - `"very_negative"` - `"negative"` - `"neutral"` - `"positive"` - `"very_positive"` - `"mixed"` - `sentiment_score_max: optional number` Filter by sentiment_score <= sentiment_score_max (inclusive). Records that have not been enriched are excluded. - `sentiment_score_min: optional number` Filter by sentiment_score >= sentiment_score_min (inclusive). The score is continuous where the label is bucketed, so this is the parameter for "the most negative feedback". Records that have not been enriched are excluded. - `since: optional string` Filter by collected_at >= since (ISO 8601 format). Must be between 1970-01-01 and 2080-12-31. - `sort: optional "collected_at" or "created_at"` Column to order by. Defaults to collected_at. Only columns that are non-null and immutable after insert are offered: a mutable sort key would let a row move across the cursor between pages and be silently skipped. - `"collected_at"` - `"created_at"` - `source_id: optional array of string` Filter by source ID. Repeat the parameter to match any of several sources; the values are OR-ed. NULL bytes not allowed. - `source_name: optional array of string` Filter by source display name — the human-readable label stored alongside source_id. Prefer source_id where the records carry one: a name can be edited or translated, while the id is stable. Repeat the parameter to match any of several names; the values are OR-ed. NULL bytes not allowed. - `source_type: optional array of string` Filter by source type. Repeat the parameter to match any of several source types; the values are OR-ed. NULL bytes not allowed. - `submission_id: optional array of string` Filter by submission ID to group records belonging to one logical submission. Repeat the parameter to match any of several submissions; the values are OR-ed, and a single occurrence behaves exactly as it always has. Comma-separated values are NOT split. NULL bytes not allowed. - `until: optional string` Filter by collected_at <= until (ISO 8601 format). Must be between 1970-01-01 and 2080-12-31. - `user_id: optional array of string` Filter by end-user identifier — everything one person submitted. Repeat the parameter to match any of several users; the values are OR-ed. NULL bytes not allowed. - `value_date_max: optional string` Filter by value_date <= value_date_max (ISO 8601, inclusive). Records whose value_date is NULL are excluded. - `value_date_min: optional string` Filter by value_date >= value_date_min (ISO 8601, inclusive) — bounds the answer to a date question, not when the feedback was collected. Records whose value_date is NULL are excluded. - `value_id: optional array of string` Filter by the source system's stable option id (e.g. all records for one survey choice). Repeat the parameter to match any of several options; the values are OR-ed. NULL bytes not allowed. - `value_number_max: optional number` Filter by value_number <= value_number_max (inclusive). Paired with value_number_min it selects a band: 9..10 is the NPS promoters, 0..6 the detractors. Records whose value_number is NULL are excluded. Supplying a max below the min is a 400, not an empty result. - `value_number_min: optional number` Filter by value_number >= value_number_min (inclusive) — e.g. NPS promoters with value_number_min=9. Records whose value_number is NULL (every non-numeric answer) are excluded, so this is never a no-op filter. ### Returns - `data: array of FeedbackRecordData` List of feedback records - `id: string` UUIDv7 primary key - `collected_at: string` When the feedback was collected - `created_at: string` When this record was created - `field_id: string` Identifier for the question/field - `field_type: "text" or "categorical" or "nps" or 6 more` Type of field - `"text"` - `"categorical"` - `"nps"` - `"csat"` - `"ces"` - `"rating"` - `"number"` - `"boolean"` - `"date"` - `source_type: string` Type of feedback source - `submission_id: string` Identifier for the logical submission this record belongs to (required). - `tenant_id: string` Tenant/organization identifier. NULL bytes not allowed. - `updated_at: string` When this record was last updated - `emotions: optional array of "joy" or "anger" or "sadness" or 3 more` Emotions inferred from value_text (emotion enrichment); multi-label from a fixed set. Read-only; absent until the record is enriched, and never an empty array. - `"joy"` - `"anger"` - `"sadness"` - `"fear"` - `"surprise"` - `"disgust"` - `field_group_id: optional string` Stable identifier grouping related fields (for ranking, matrix, grid questions) - `field_group_label: optional string` Human-readable question text for the group - `field_label: optional string` The actual question text - `language: optional string` ISO language code. NULL bytes not allowed. - `metadata: optional unknown or array of unknown or string or 2 more` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `UnionMember0 = unknown` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `UnionMember1 = array of unknown` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `UnionMember2 = string` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `UnionMember3 = number` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `UnionMember4 = boolean` Arbitrary context stored with this record, returned with equivalent values (key order is normalized, and so are number exponents — `1e2` reads back as `100`). Omitted when the record has no metadata; a record explicitly stored with a JSON `null` returns `null`. An object is the supported shape and the only one the request schemas accept. The wider union is for reading: the column stores whatever JSON was written, so a record created before that convention can return an array or a scalar, and an object-only response schema would make a generated client reject it. - `sentiment: optional "very_negative" or "negative" or "neutral" or 3 more` Sentiment polarity inferred from value_text (sentiment enrichment). Read-only; absent until the record is enriched. - `"very_negative"` - `"negative"` - `"neutral"` - `"positive"` - `"very_positive"` - `"mixed"` - `sentiment_score: optional number` Signed sentiment polarity from -1.0 (very negative) to 1.0 (very positive) (sentiment enrichment). Read-only; absent until the record is enriched. - `source_id: optional string` Reference to survey/form/ticket ID - `source_name: optional string` Human-readable name - `translation_lang_key: optional string` BCP-47 target locale that value_text_translated was produced in (language enrichment). Read-only; absent until the record is enriched. - `user_id: optional string` User ID (e.g., anonymous ID or email hash) - `value_boolean: optional boolean` Boolean response - `value_date: optional string` Date response - `value_id: optional string` Stable id of the selected option in the source system (e.g. a survey choice id), stored alongside value_text for durable option identity. Opaque to Hub. Absent for free-text/non-choice answers. NULL bytes not allowed. - `value_number: optional number` Numeric response - `value_text: optional string` Text response. NULL bytes not allowed. - `value_text_translated: optional string` value_text translated into the tenant's configured target language (language enrichment). Read-only; absent until the record is enriched. - `limit: number` Limit used in query - `next_cursor: optional string` Opaque cursor for the next page (keyset paging). Present only when there may be more results. Use as the cursor query param for the next page. ### Example ```http curl http://localhost:8080/v1/feedback-records \ -H "Authorization: Bearer $HUB_API_KEY" ```