Shared behavior
Every/v1 endpoint follows the same rules, so what you learn on one endpoint applies to all of them. The API is read-only: every endpoint is a GET, and nothing is mutated. Feedback is ingested through your connected integrations, not this API.
Authentication
Send your key on every request as anX-API-Key header:
apikey is also accepted as an alias for X-API-Key.
Keys are team-scoped (every response is limited to your team’s data) and region-scoped (a key issued in the EU only works against https://eu.api.closedloop.sh/v1). Create keys in Settings, API Keys in the app. See API keys for the full lifecycle.
API-key authentication is designed for unattended server workloads, including scheduled Databricks jobs and agents with no person present at run time.
Pagination
Offset pagination
List endpoints acceptlimit and offset:
Responses wrap the page in
data and include a pagination object whose total is the full-set count of matching records, not the size of the page:
offset until you have read total records.
Incremental insight export
For a repeatable/insights export, make the first request with updated_since instead of offset:
updated_at, then id, and uses a fixed upper watermark:
cursor and, optionally, a different limit:
updated_since < updated_at <= sync_until. Save sync_until as your next checkpoint only after the final page returns has_more: false. Upsert rows by immutable id; replaying the last committed window gives at-least-once recovery after a failed run.
Treat cursors as opaque. They are signed and bound to the team, API key, filters, and watermark. If the key is rotated during a run, restart from the last committed sync_until with the new key. Cursor exports do not include a total because the fixed window and has_more determine completion.
Filtering
Filters are query parameters, and multiple filters combine with AND semantics:- Free-text search: most list endpoints accept
q, a free-text search over titles and content. - Date ranges:
date_fromanddate_toare inclusive ISO 8601 dates. - Product scope: insights, themes, and coverage accept
product_id,product_feature_id,product_area_id, andfeature_area_id. Features acceptproduct_id,feature_area_id,product_area_idandproduct_feature_id(which on that endpoint selects the named feature itself). - Resource-specific filters: each endpoint documents its own. For example, insights filter by
category,severity, andcustomer_id; themes acceptstatusandsort; features accepttheme_id; context acceptstype.
Theme search
GET /themes?q=... uses lexical search by default: the complete q value must occur as
one case-insensitive substring in the theme title or description.
For meaning-based matches, use:
q accepts at most 500 characters. min_similarity is optional, defaults to 0.50,
and accepts 0.30 through 1.00.
Semantic results are ordered by similarity descending and then immutable theme ID, so
semantic relevance overrides sort. If ClosedLoop AI cannot generate the query embedding,
the request returns 503 SEMANTIC_SEARCH_UNAVAILABLE by default. To permit lexical
fallback, add allow_fallback=true. Read the response’s search_mode and
min_similarity: an allowed fallback returns "search_mode": "lexical" and
"min_similarity": null.
Call /products, /features, and /areas to discover the current product-scope IDs. Insight responses expose the same IDs so filters are round-trippable:
Each
product_features[] item also includes its resolved title and nullable product_id. Call /facets for categories, severities, and sources.
product_feature_id and feature_area_id name two different kinds of ID and are never interchangeable. A product feature is a buildable item from /features, identified by a feature ID; /features/{id} takes that ID. A feature area is the subject area an insight is filed under, listed by /areas. GET /features?feature_area_id=... therefore selects features by the area of their supporting insights, and scopes each returned feature’s insight_count and unique_customer_count to that area; features with no evidence in the area are omitted. product_area_id does the same one level up, across every active child area of that product area. sort=ric_score stays the feature’s stored overall score in that case, while sort=insight_count and sort=unique_customer_count rank on the scoped values.
Product filtering returns insights with a recorded association. Some insights remain unassigned when ClosedLoop AI cannot determine a reliable automatic match, so filtered results may not include every potentially relevant insight. Fully unassociated insights return both arrays empty. A feature can also appear with product_id: null; in that case products is empty while product_features is not.
Stable IDs
Insight and theme IDs are immutable UUIDs and are never reassigned or recycled to another resource. If a resource is removed, a later lookup returns404; the API does not reuse its ID. If a theme is merged, GET /themes/{id} for the retired ID returns 410 Gone with code THEME_RETIRED and the final surviving replacement_theme_id. To list current themes together with old merged themes, use GET /themes?include_retired=true; each merged row has the same final pointer, so callers never need to follow a chain. Names, descriptions, theme membership, and aggregate counts can evolve as ClosedLoop AI processes more feedback.
Errors
Non-2xx responses return a JSON body with a user-safe message, a stable code, an optional hint, and any documented error-specific fields. Internal details are never exposed./v1 is strict about query parameters: anything an endpoint does not support is
rejected with 400 UNSUPPORTED_QUERY_PARAMETER rather than ignored. A filter that
silently did nothing would be indistinguishable from one that worked, so a rejection
is the only way to tell. Support is PER ENDPOINT — search_mode is valid on
/themes and /features and rejected on /coverage.
Rate limits
The public API has no general fixed per-key quota. Semantic search uses implementation-defined protective rate and concurrency safeguards. Clients must handle429 and honor Retry-After; these safeguards are enforced independently by API replica
and are not a fleet-wide numeric quota.
/themes and /features share ONE semantic-search budget; they are not separately
allowanced. Semantic traffic on either endpoint can therefore produce a 429 on the
other.
Timestamps
All timestamps are ISO 8601 in UTC, for example2026-05-14T10:30:00Z. Date filters (date_from, date_to) accept ISO 8601 dates and are inclusive on both ends. source_date describes when the feedback occurred; updated_at is the incremental export cursor timestamp.
Linking insights and context
Insight and context records carry the same resolvedcustomer_id, so you can line up an insight with the strategic context (churn, competitor, satisfaction) for the same customer. Filter either list by customer_id.
New to the API? Start with the API reference introduction.