Feedback Records

List feedback records with filters

Changed on

Lists feedback records for a required tenant_id with optional additional filters and pagination

get/v1/feedback-records

Request

  • Base URL
    http://localhost:8080
  • URL
    http://localhost:8080/v1/feedback-records
  • Auth
    HTTP bearer

Query parameters

tenant_idstring required

Tenant ID (required for isolation). NULL bytes not allowed.

Example:org-123
submission_idstring[]

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.

[
  "550e8400-e29b-41d4-a716-446655440000"
]
source_typestring[]

Filter by source type. Repeat the parameter to match any of several source types; the values are OR-ed. NULL bytes not allowed.

[
  "survey",
  "review"
]
source_idstring[]

Filter by source ID. Repeat the parameter to match any of several sources; the values are OR-ed. NULL bytes not allowed.

[
  "survey-123"
]
field_idstring[]

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.

[
  "q1"
]
field_group_idstring[]

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.

[
  "feature_priority"
]
field_typeFieldType[]

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",
  "rating"
]
value_idstring[]

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.

[
  "opt_very_satisfied"
]
user_idstring[]

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.

[
  "user-abc-123"
]
source_namestring[]

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.

[
  "After match survey"
]
languagestring[]

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.

[
  "en",
  "ar"
]
sincestring date-time

Filter by collected_at >= since (ISO 8601 format). Must be between 1970-01-01 and 2080-12-31.

Example:2024-01-01T00:00:00Z
untilstring date-time

Filter by collected_at <= until (ISO 8601 format). Must be between 1970-01-01 and 2080-12-31.

Example:2024-12-31T23:59:59Z
created_sincestring date-time
Example:2026-01-01T00:00:00Z

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_untilstring date-time
Example:2026-12-31T00:00:00Z

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.

updated_sincestring date-time
Example:2026-01-01T00:00:00Z

Filter by updated_at >= updated_since (ISO 8601, inclusive). updated_at is stamped on every write to a record — create, update, and every enrichment result (translation, sentiment, emotions) — so this selects records created OR changed since a point: the parameter for incremental extraction. Pair it with sort=updated_at&order=asc. For the next run, use the time the previous run started (its first response's Date header) minus an overlap, not the largest updated_at received. The overlap must outlast the longest write to a record; Hub does not bound that, so a few minutes is a default rather than a guarantee. Permanently deleted records are never returned: periodically list every record, upsert what it returns (which also repairs any change a slow write slipped past the overlap), and remove only local records that were not returned and were created before that listing started (its first response's Date header) minus the overlap. See "Incremental extraction" in the filtering and sorting guide.

updated_untilstring date-time
Example:2026-12-31T00:00:00Z

Filter by updated_at <= updated_until (ISO 8601, inclusive). See updated_since for what updates updated_at.

value_number_minnumber double
Example:9

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.

value_number_maxnumber double
Example:10

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_date_minstring date-time
Example:2026-01-01T00:00:00Z

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_date_maxstring date-time
Example:2026-12-31T00:00:00Z

Filter by value_date <= value_date_max (ISO 8601, inclusive). Records whose value_date is NULL are excluded.

sentimentSentimentValue[]

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.

[
  "negative",
  "very_negative"
]
emotionsEmotionValue[]

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.

[
  "anger",
  "fear"
]
sentiment_score_minnumber double
Example:-0.5

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.

sentiment_score_maxnumber double
Example:0.5

Filter by sentiment_score <= sentiment_score_max (inclusive). Records that have not been enriched are excluded.

has_sentimentboolean
Example:true

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_emotionsboolean
Example:true

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_translationboolean

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.

sort'collected_at' | 'created_at' | 'updated_at'

Column to order by. Defaults to collected_at. collected_at and created_at never change. updated_at does, so it is accepted only with order=asc: ascending, a record that changes while you page normally moves ahead of the cursor and is returned again (upsert it by id); descending, every such record would land behind the cursor and be lost, so that combination is a 400. Ascending still misses a write that started before the cursor's position and commits after it; the next run picks it up when its updated_since is the previous run's start minus an overlap. Use sort=updated_at&order=asc with updated_since and updated_until for incremental extraction, both derived from the Date header of one request made at the run's start (see the filtering and sorting guide): every page is one ordered index range.

order'asc' | 'desc'

Sort direction. Defaults to desc, so sort=updated_at needs order=asc given explicitly. Rows tied on the sort column are ordered by id ascending.

limitinteger

Number of results to return (max 1000)

cursorstring
Example:eyJ0IjoiMjAyNC0wMS0xNVQxMDozMDowMFoiLCJpIjoiMDE4ZTEyMzQtNTY3OC05YWJjLWRlZjAtMTIzNDU2Nzg5YWJjIn0=

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.

Response

OK

limitinteger required

Limit used in query

next_cursorstring

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.

Changes

    • info

      added the new optional query request parameter updated_since

    • info

      added the new optional query request parameter updated_until

    • info

      added the new enum value updated_at to the query request parameter sort

    • info

      added the optional property // to the response with the status