{"openapi":"3.1.0","info":{"title":"Customer Conversations Client Intelligence API","version":"1.2.0","description":"Client-scoped read-only API for quality-gated interviews, campaign context, and published synthesis documents (the current intelligence layer).\n\nThe API defaults to safe metadata and published intelligence. Full transcripts are available only when the API key includes read:transcripts. Customer email on interview resources and identity-carrying daily_feed synthesis require read:customer_identity. Raw and custom extraction require their own elevated scopes. Other published synthesis surfaces are available with the default read:synthesis scope.\n\nEvery list endpoint supports keyset cursor pagination: pass limit and the previous page's meta.next_cursor as starting_after. meta.has_more tells you whether more rows exist; meta.next_cursor is null when the collection is exhausted. Walks are deterministic — no gaps, no duplicates, stable under concurrent inserts. updated_since is supported per-route only where the underlying table has a real updated_at column (campaigns, synthesis); interviews has none.\n\nRate limiting: every key-authenticated route allows 600 requests per rolling hour per API key. Every authenticated response carries X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset; beyond the limit, requests return 429 with error code rate_limited and a Retry-After header giving the seconds until the oldest counted request ages out of the window. Rate-limited (429) responses do not consume quota. This unauthenticated openapi.json document is not rate limited."},"servers":[{"url":"https://app.customerconversations.co"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Synthesis"},{"name":"Interviews"},{"name":"Campaigns"}],"paths":{"/api/client/v1/interviews":{"get":{"summary":"List quality-gated interview metadata","description":"Returns only reportable/client-safe/quality-passed interviews. Transcript and customer email are omitted unless elevated scopes are present. updated_since is NOT supported here: the interviews table has no updated_at column (requests carrying it get a 400 with code updated_since_unsupported); use date_from/date_to on created_at for time-bounded pulls.\n\nRequired scope: read:interviews_metadata.","x-measurement-integrity":"canonical-interview-membership","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 50, max 200).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"starting_after","in":"query","required":false,"description":"Opaque keyset cursor: pass the previous page's meta.next_cursor to fetch the next page. Stable total order: created_at DESC, id ASC. Malformed cursors return 400 with error code invalid_cursor.","schema":{"type":"string"}},{"name":"campaign_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_from","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_to","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful client-scoped response.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewListResponse"}}}},"400":{"description":"Invalid list parameters. Error codes: invalid_cursor — starting_after is malformed, truncated, or was issued by a different route; invalid_updated_since — updated_since is not a valid ISO 8601 date or date-time; updated_since_unsupported — this route's table has no updated_at column.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/client/v1/interviews/{id}":{"get":{"summary":"Get one quality-gated interview","description":"With read:transcripts, the response includes the full transcript. With read:customer_identity, it includes participant_email. With extraction scopes, it includes raw_extraction and/or custom_extraction.\n\nRequired scope: read:interviews_metadata.","x-measurement-integrity":"canonical-interview-membership","security":[{"bearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Successful client-scoped response.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewDetailResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No quality-gated interview with this id is visible to the client.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/client/v1/campaigns":{"get":{"summary":"List conversation goals and campaign context","description":"Use campaigns to scope analysis by current client research questions. Interview rows reference campaigns via conversation_goal_id. The status filter matches the campaign lifecycle column launch_status. updated_since filters on the row's real updated_at.\n\nRequired scope: read:campaigns.","x-measurement-integrity":"non-measurement-resource","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 50, max 200).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"starting_after","in":"query","required":false,"description":"Opaque keyset cursor: pass the previous page's meta.next_cursor to fetch the next page. Stable total order: created_at DESC NULLS LAST, id ASC. Malformed cursors return 400 with error code invalid_cursor.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Return only rows whose updated_at is at or after this ISO 8601 timestamp. Invalid values return 400 with error code invalid_updated_since.","schema":{"type":"string","format":"date-time"}},{"name":"status","in":"query","required":false,"schema":{"type":"string"}},{"name":"campaign_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_from","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_to","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful client-scoped response.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignListResponse"}}}},"400":{"description":"Invalid list parameters. Error codes: invalid_cursor — starting_after is malformed, truncated, or was issued by a different route; invalid_updated_since — updated_since is not a valid ISO 8601 date or date-time; updated_since_unsupported — this route's table has no updated_at column.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/client/v1/synthesis":{"get":{"summary":"Read published portal synthesis documents","description":"The default is the backward-compatible paginated published-document collection. type filters collection rows and lane_id/campaign_id preserve routed collection reads. Add surface=portal for frozen portal bodies: portal type supports home, daily_feed, lane, campaign, playbook, and creative_concept; daily_feed accepts day or date_from/date_to, while lane and campaign accept visible slugs. updated_since always applies to collection mode.\n\nRequired scope: read:synthesis.","x-measurement-integrity":"published-artifact-verbatim","security":[{"bearerAuth":[]}],"parameters":[{"name":"limit","in":"query","required":false,"description":"Page size (default 50, max 200).","schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"starting_after","in":"query","required":false,"description":"Opaque keyset cursor: pass the previous page's meta.next_cursor to fetch the next page. Stable total order: last_refreshed DESC NULLS LAST, updated_at DESC, id ASC. Malformed cursors return 400 with error code invalid_cursor.","schema":{"type":"string"}},{"name":"updated_since","in":"query","required":false,"description":"Return only rows whose updated_at is at or after this ISO 8601 timestamp. Invalid values return 400 with error code invalid_updated_since.","schema":{"type":"string","format":"date-time"}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"lane_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"campaign_id","in":"query","required":false,"schema":{"type":"string"}},{"name":"surface","in":"query","required":false,"schema":{"type":"string"}},{"name":"day","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_from","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_to","in":"query","required":false,"schema":{"type":"string"}},{"name":"lane","in":"query","required":false,"schema":{"type":"string"}},{"name":"campaign","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Successful client-scoped response.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SynthesisListResponse"}}}},"400":{"description":"Invalid list parameters. Error codes: invalid_cursor — starting_after is malformed, truncated, or was issued by a different route; invalid_updated_since — updated_since is not a valid ISO 8601 date or date-time; updated_since_unsupported — this route's table has no updated_at column.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/client/v1/analytics/interviews":{"get":{"summary":"Read portal-parity interview analytics","description":"Uses the same reportable corpus and chart builders as the portal. Defaults to the portal 30-day window, anchored to canonical measurement as_of after cutover and request time for legacy tenants; explicit date_from/date_to override it. Required scope: read:interviews_metadata.","security":[{"bearerAuth":[]}],"parameters":[{"name":"campaign","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_from","in":"query","required":false,"schema":{"type":"string"}},{"name":"date_to","in":"query","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Volume, campaign, sentiment, and pulse-row series.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterviewAnalyticsResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/api/client/v1/intelligence/search":{"get":{"summary":"Search usable-corpus customer intelligence","description":"Searches client-safe custom extraction only after tenant, usable-interview, reportability, and live-campaign exclusions. Required scope: read:custom_extraction.","security":[{"bearerAuth":[]}],"parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":1}},{"name":"campaign","in":"query","required":false,"schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Matching intelligence records and matched field names.","headers":{"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntelligenceSearchResponse"}}}},"400":{"description":"Invalid list parameters. Error codes: invalid_cursor — starting_after is malformed, truncated, or was issued by a different route; invalid_updated_since — updated_since is not a valid ISO 8601 date or date-time; updated_since_unsupported — this route's table has no updated_at column.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing, invalid, revoked, or expired API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The API key is valid but does not include the required scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Rate limited: this API key has already made 600 requests within the rolling hour. The body carries error code rate_limited; Retry-After gives the seconds until the oldest counted request ages out of the window.","headers":{"Retry-After":{"description":"Seconds until capacity frees (the oldest counted request ages out of the rolling window).","schema":{"type":"integer"}},"X-RateLimit-Limit":{"description":"The rolling-hour request limit per API key (600).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests still available in the current rolling hour at admission time, including this request.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix timestamp (seconds) when the oldest counted request ages out of the rolling window and capacity next frees.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"description":"Canonical measurement was enabled but could not be proven for this response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"Customer Conversations client API key","description":"Send the API key as Authorization: Bearer <CLIENT_API_KEY>."}},"schemas":{"InterviewAnalyticsResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"total":{"type":"integer","minimum":0},"volume":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}},"sentiment":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsSeriesPoint"}},"rows":{"type":"array","items":{"$ref":"#/components/schemas/AnalyticsPulseRow"}}},"required":["total","volume","campaigns","sentiment","rows"],"additionalProperties":false},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"],"additionalProperties":false},"AnalyticsSeriesPoint":{"type":"object","properties":{"label":{"type":"string"},"value":{"type":"integer","minimum":0}},"required":["label","value"],"additionalProperties":false},"AnalyticsPulseRow":{"type":"object","properties":{"date":{"type":"string","format":"date"},"campaignSlug":{"type":"string"},"campaignLabel":{"type":"string"},"sentiment":{"type":"string","enum":["promoter","passive","detractor","unknown"]}},"required":["date","campaignSlug","campaignLabel","sentiment"],"additionalProperties":false},"IntelligenceSearchResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/IntelligenceSearchHit"}},"meta":{"allOf":[{"$ref":"#/components/schemas/Meta"},{"type":"object","properties":{"query":{"type":"string"}},"required":["query"]}]}},"required":["data","meta"],"additionalProperties":false},"IntelligenceSearchHit":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"conversation_goal":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"best_quote":{"type":["string","null"]},"primary_pain_points":{"type":["string","null"]},"feature_requests":{"type":["string","null"]},"product_feedback":{"type":["string","null"]},"competitor_mentions":{"type":["string","null"]},"matched_fields":{"type":"array","items":{"type":"string","enum":["best_quote","primary_pain_points","feature_requests","product_feedback","competitor_mentions"]},"minItems":1}},"required":["id","conversation_goal","created_at","best_quote","primary_pain_points","feature_requests","product_feedback","competitor_mentions","matched_fields"],"additionalProperties":false},"ErrorResponse":{"type":"object","properties":{"error":{"type":"string"},"details":{"type":"string"},"code":{"type":"string"}},"required":["error"]},"Meta":{"type":"object","properties":{"client_id":{"type":"string","format":"uuid"},"count":{"type":"integer"},"has_more":{"type":"boolean","description":"True when more rows exist beyond this page (list endpoints)."},"next_cursor":{"type":["string","null"],"description":"Opaque cursor for the next page — pass as starting_after. Null when the collection is exhausted."},"types":{"type":"array","items":{"type":"string"},"description":"The synthesis_type set this /synthesis response was filtered to."},"type":{"type":"string","enum":["home","daily_feed","lane","campaign","playbook","creative_concept"],"description":"The typed portal synthesis surface returned by this response."},"includes_customer_identity":{"type":"boolean"},"includes_transcripts":{"type":"boolean"},"includes_raw_extraction":{"type":"boolean"},"includes_custom_extraction":{"type":"boolean"},"measurement_basis":{"$ref":"#/components/schemas/MeasurementQueryBasis"},"measurement_mode":{"type":"string","enum":["legacy","canonical"]},"measurement_total_count":{"type":"integer","minimum":0,"description":"Unfiltered canonical reportable membership at measurement_basis.as_of; independent of filters and page limit."},"measurement_as_of":{"type":"string","format":"date-time"},"measurement_contract_version":{"type":"string"},"measurement_count_basis":{"type":"string"},"measurement_omitted_fields":{"type":"array","items":{"type":"string"}}},"required":["client_id","count"]},"MeasurementQueryBasis":{"type":"object","properties":{"schema":{"type":"string","const":"measurement_query_basis.v1"},"preview_id":{"type":"string","format":"uuid"},"captured":{"type":"boolean","const":false},"as_of":{"type":"string","format":"date-time"},"measurement_contract_version":{"type":"string"},"scope_hash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"membership_hash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"source_interview_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"source_count":{"type":"integer","minimum":0}},"required":["schema","preview_id","captured","as_of","measurement_contract_version","scope_hash","membership_hash","source_interview_ids","source_count"],"additionalProperties":false},"SavedMeasurementBasis":{"type":"object","properties":{"schema":{"type":"string","const":"measurement_basis.v2"},"snapshot_id":{"type":"string","format":"uuid"},"as_of":{"type":"string","format":"date-time"},"measurement_contract_version":{"type":"string"},"scope_hash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"membership_hash":{"type":"string","pattern":"^[0-9a-f]{64}$"},"source_interview_ids":{"type":"array","items":{"type":"string","format":"uuid"}},"source_count":{"type":"integer","minimum":0}},"required":["schema","snapshot_id","as_of","measurement_contract_version","scope_hash","membership_hash","source_interview_ids","source_count"],"additionalProperties":false},"MeasurementIntegrity":{"type":"object","properties":{"status":{"type":"string","enum":["verified_saved_basis","historical_unverifiable"]},"measurement_basis":{"$ref":"#/components/schemas/SavedMeasurementBasis"},"freshness":{"type":"object","properties":{"status":{"type":"string","enum":["comparable","incomparable"]},"reason":{"type":["string","null"],"enum":["contract_changed","scope_changed",null]},"saved_as_of":{"type":"string","format":"date-time"},"live_as_of":{"type":"string","format":"date-time"},"saved_contract_version":{"type":"string"},"live_contract_version":{"type":"string"}},"required":["status","reason","saved_as_of","live_as_of","saved_contract_version","live_contract_version"],"additionalProperties":false}},"required":["status"],"additionalProperties":false},"SynthesisDocumentRow":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"client_id":{"type":"string","format":"uuid"},"synthesis_type":{"type":"string","enum":["lane","campaign","home","daily_feed","playbook","creative_concept"]},"lane_id":{"type":["string","null"],"format":"uuid"},"conversation_goal_id":{"type":["string","null"],"format":"uuid"},"document":{"type":"object","description":"The published document payload. Its shape is versioned by contract_version.","additionalProperties":true},"contract_version":{"type":"string"},"status":{"type":"string","const":"published"},"cadence":{"type":["string","null"]},"feed_date":{"type":["string","null"],"format":"date"},"last_refreshed":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}},"required":["id","client_id","synthesis_type","document","contract_version","status","created_at","updated_at"],"additionalProperties":false},"PublishedSynthesisBody":{"type":"object","description":"A frozen, published portal document body. Its schema discriminator and fields are the same contract consumed by the corresponding portal surface. creative_concept returns the client-visible creative lane together with its concept annotations.","additionalProperties":true},"SynthesisDocument":{"description":"A legacy collection row when surface is omitted, or a published portal body only when surface=portal. In collection mode, type filters rows and supports lane, campaign, home, and daily_feed; playbook and creative_concept are portal-only.","oneOf":[{"$ref":"#/components/schemas/SynthesisDocumentRow"},{"$ref":"#/components/schemas/PublishedSynthesisBody"}]},"Interview":{"type":"object","description":"A quality-gated interview as projected by the interviews routes. Scope-gated fields (participant_email, transcript, raw_extraction, custom_extraction) are OMITTED from the response entirely when the key lacks the scope. raw_extraction is returned only by the detail route.","properties":{"id":{"type":"string","format":"uuid"},"client_id":{"type":"string","format":"uuid"},"conversation_goal_id":{"type":["string","null"],"format":"uuid"},"participant_name":{"type":["string","null"]},"participant_email":{"type":["string","null"],"description":"Included only with read:customer_identity."},"interview_type":{"type":["string","null"]},"status":{"type":"string"},"duration_seconds":{"type":["number","null"]},"nps_score":{"type":["number","null"]},"customer_sentiment":{"type":["string","null"]},"churn_risk":{"type":["string","null"]},"best_quote":{"type":["string","null"]},"transcript":{"type":["array","null"],"description":"Full transcript turns. Included only with read:transcripts.","items":{"type":"object","additionalProperties":true}},"raw_extraction":{"type":["object","null"],"description":"Raw ElevenLabs/provider extraction. Detail route only, with read:raw_extraction.","additionalProperties":true},"custom_extraction":{"type":["object","null"],"description":"Cleaned workbook/report-facing extraction. Included only with read:custom_extraction.","additionalProperties":true},"created_at":{"type":"string","format":"date-time"}},"required":["id","client_id","status","created_at"]},"Campaign":{"type":"object","description":"A conversation goal (campaign) as projected by /campaigns — the exact column list the route selects from conversation_goals.","properties":{"id":{"type":"string","format":"uuid"},"client_id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"display_name":{"type":["string","null"]},"campaign_brief":{"type":["string","null"]},"goal":{"type":["string","null"]},"goal_context":{"type":["string","null"]},"key_questions":{"description":"Ordered key-question list ([{ id, question }] canonical; legacy rows may carry a plain string array)."},"target_segment":{"type":["string","null"]},"playbook":{"type":["string","null"]},"cadence":{"type":["string","null"]},"active":{"type":["boolean","null"]},"launch_status":{"type":["string","null"],"description":"Campaign lifecycle status; the status query filter matches this column."},"launched_at":{"type":["string","null"],"format":"date-time"},"last_refreshed":{"type":["string","null"],"format":"date-time"},"created_at":{"type":["string","null"],"format":"date-time"},"updated_at":{"type":["string","null"],"format":"date-time"}},"required":["id","client_id","slug"],"additionalProperties":false},"InterviewListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Interview"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]},"InterviewDetailResponse":{"type":"object","properties":{"data":{"type":"object","properties":{"interview":{"$ref":"#/components/schemas/Interview"}},"required":["interview"]},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]},"CampaignListResponse":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Campaign"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]},"SynthesisListResponse":{"type":"object","properties":{"data":{"type":"array","description":"When surface is omitted, returns paginated SynthesisDocumentRow records; type is only a collection filter for lane, campaign, home, or daily_feed. Only surface=portal returns published portal bodies, including playbook and creative_concept.","items":{"$ref":"#/components/schemas/SynthesisDocument"}},"meta":{"$ref":"#/components/schemas/Meta"}},"required":["data","meta"]}}},"x-customer-conversations":{"default_scopes":["read:campaigns","read:interviews_metadata","read:synthesis"],"elevated_scopes":["read:transcripts","read:customer_identity","read:raw_extraction","read:custom_extraction","export:data"],"recommended_agent_order":["/api/client/v1/synthesis","/api/client/v1/campaigns","/api/client/v1/interviews","/api/client/v1/interviews/{id} when deeper context or transcript access is needed"],"measurement_inventory":[{"path":"/api/client/v1/interviews","measurement":"canonical-interview-membership"},{"path":"/api/client/v1/interviews/{id}","measurement":"canonical-interview-membership"},{"path":"/api/client/v1/campaigns","measurement":"non-measurement-resource"},{"path":"/api/client/v1/synthesis","measurement":"published-artifact-verbatim"},{"path":"/api/client/v1/openapi.json","measurement":"documentation-only"}],"rate_limit":{"limit_per_hour":600,"window":"rolling 1 hour","scope":"per API key","counted":"all key-attributed request rows logged under /api/client/* endpoints — including 401s from wrong-secret, revoked, or expired keys carrying a real key prefix (the 401 response itself is returned before the limiter runs, but its logged row counts toward that key's later windows). 429 responses are not counted.","over_limit":{"status":429,"error_code":"rate_limited","headers":["Retry-After","X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset"]}},"list_contracts":{"interviews":{"pagination":"keyset","ordering":"created_at DESC, id ASC","updated_since":false},"campaigns":{"pagination":"keyset","ordering":"created_at DESC NULLS LAST, id ASC","updated_since":true},"synthesis":{"pagination":"keyset","ordering":"last_refreshed DESC NULLS LAST, updated_at DESC, id ASC","updated_since":true}}}}