API — Episodes
API — Episodes
Section titled “API — Episodes”Purpose
Section titled “Purpose”The episodes endpoints provide access to conversation history as discrete episodes. Each episode represents a conversation thread between a user and the AI assistant. The API supports listing episodes, viewing transcripts, looking up by response ID, tracking unread state, and a BFF transformation layer that reshapes episode data for mobile card rendering.
Architecture: Rover-agent acts as a BFF (Backend for Frontend) that proxies requests to the consumer-agent Python service. BFF endpoints transform the response into an ordered array shape optimized for mobile card rendering.
Authentication
Section titled “Authentication”All episodes endpoints require authentication via the authenticatedAPI middleware chain.
Required Headers:
X-API-Key— API key for service authenticationAuthorization— JWT bearer token for user authenticationuserId— User identifier (extracted from JWT in production)
Endpoints
Section titled “Endpoints”| Method | Path | Description |
|---|---|---|
| GET | /api/v1/episodes | List episodes |
| GET | /api/v1/episodes/{episode_id} | Get episode transcript |
| GET | /api/v1/episodes/unread/count | Get unread episode count |
| GET | /api/v1/episodes/unread | List unread episodes with transcripts |
| GET | /api/v1/episodes/bff | Episodes BFF |
| GET | /api/v1/episodes/bff/unread | Unread episodes BFF |
GET /api/v1/episodes
Section titled “GET /api/v1/episodes”List the authenticated user’s episodes sorted by most recent activity.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 20 | Number of episodes to return (1–100) |
cursor | string | — | Pagination cursor from previous response |
Response — 200 OK
Section titled “Response — 200 OK”{ "episodes": [ { "id": "968aed30-a619-4ac5-a00d-f4a3a581e9cd", "title": "Daily Deals Alert", "displayDate": "3 hours ago", "is_unread": true, "image_url": "https://cdn.fetchrewards.com/test/hero-deals.png", "icon_url": "https://cdn.fetchrewards.com/test/icon-deals.png", "subtitle": "3 new offers near you", "cta_label": "See Deals", "cta_action": "deeplink://offers/daily" }, { "id": "c0c48608-c1b4-479d-b75a-98a7acb1cdda", "title": "What cereal has the most points?", "displayDate": "Yesterday", "is_unread": false, "image_url": null, "icon_url": null, "subtitle": null, "cta_label": null, "cta_action": null } ], "pagination": { "next_cursor": "eyJVc2VySWQiOi..." }, "unread_count": 3}Field Definitions
Section titled “Field Definitions”EpisodeSummary:
| Field | Type | Description |
|---|---|---|
id | string | Unique episode identifier (UUID) |
title | string | Episode title — first user message or explicitly set title, truncated to 120 chars |
displayDate | string | Relative timestamp: “Just now”, “5 minutes ago”, “3 hours ago”, “Yesterday”, “3 days ago”, “Jan 15” |
is_unread | bool | true if the episode was created by the episode builder and has not been opened |
image_url | string | null | Hero image URL for BFF card rendering (set by episode builder) |
icon_url | string | null | Icon URL for BFF card rendering (set by episode builder) |
subtitle | string | null | Subtitle text for BFF card (set by episode builder) |
cta_label | string | null | CTA button label (set by episode builder) |
cta_action | string | null | CTA deeplink URL (set by episode builder) |
Pagination:
| Field | Type | Description |
|---|---|---|
next_cursor | string | null | Opaque cursor for fetching the next page. null when no more pages. |
GET /api/v1/episodes/{episode_id}
Section titled “GET /api/v1/episodes/{episode_id}”Get the full transcript for a specific episode. Returns conversational turns (user message + AI response events). Side effect: marks unread episodes as read in the background.
Path Parameters
Section titled “Path Parameters”| Parameter | Type | Description |
|---|---|---|
episode_id | string | Episode UUID |
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 20 | Number of turns to return (1–100) |
cursor | string | — | Pagination cursor for turn pagination |
Response — 200 OK
Section titled “Response — 200 OK”{ "id": "968aed30-a619-4ac5-a00d-f4a3a581e9cd", "turns": [ { "user_query": "Show me today's deals", "ai_response": [ {"event_type": "chunk", "content": "Here are some great deals!"}, { "event_type": "data_loaded", "data": { "type": "offer_list", "key": {"ids": [{"id": "offer_001"}, {"id": "offer_002"}]}, "items": [{"offerId": "offer_001"}, {"offerId": "offer_002"}] } } ] } ], "pagination": { "next_cursor": null }}- Product cards in the transcript are enriched with current product data (title, price, image) via the FIDO Product Service.
- Prompt-suggestion components and reasoning blocks are stripped from the transcript.
- Opening a transcript marks the episode as read (background task, does not block the response).
GET /api/v1/episodes/unread/count
Section titled “GET /api/v1/episodes/unread/count”Get the count of unread episodes for the authenticated user.
Response — 200 OK
Section titled “Response — 200 OK”{ "unread_count": 3}Field Definitions
Section titled “Field Definitions”| Field | Type | Description |
|---|---|---|
unread_count | int | Number of episodes created by the episode builder that have not been opened |
Client Behavior
Section titled “Client Behavior”- Use this endpoint for badge counts on navigation tabs or app icons.
- The count only includes episodes created via the episode builder (POST /episodes), not user-initiated conversations.
- An episode becomes “read” when its transcript is fetched via GET
/api/v1/episodes/{episode_id}.
GET /api/v1/episodes/unread
Section titled “GET /api/v1/episodes/unread”List unread episodes with their full transcripts.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 10 | Number of episodes to return (1–20) |
cursor | string | — | Pagination cursor |
Response — 200 OK
Section titled “Response — 200 OK”{ "episodes": [ { "id": "968aed30-a619-4ac5-a00d-f4a3a581e9cd", "title": "Daily Deals Alert", "displayDate": "3 hours ago", "turns": [ { "user_query": "Show me today deals", "ai_response": [ {"event_type": "chunk", "content": "We found some great deals for you today!"}, {"event_type": "data_loaded", "data": {"type": "offer_list", "key": {"ids": [{"id": "offer_001"}]}}} ] } ] } ], "pagination": { "next_cursor": null }, "unread_count": 3}- This endpoint does not mark episodes as read (unlike GET
/api/v1/episodes/{episode_id}). - Transcripts include full turn data with product card enrichment.
GET /api/v1/episodes/bff
Section titled “GET /api/v1/episodes/bff”BFF endpoint. Returns episodes grouped into time-based sections optimized for mobile card rendering. Applies default values for missing BFF fields and compacts display timestamps.
Query Parameters
Section titled “Query Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
limit | int | 20 | Number of episodes to return |
cursor | string | — | Pagination cursor |
Response — 200 OK
Section titled “Response — 200 OK”{ "unread_count": 3, "sections": [ { "header": "Today", "episodes": [ { "id": "968aed30-a619-4ac5-a00d-f4a3a581e9cd", "title": "Daily Deals Alert", "subtitle": "3 new offers near you", "display_time": "3h", "image_url": "https://cdn.fetchrewards.com/test/hero-deals.png", "icon_url": "https://cdn.fetchrewards.com/test/icon-deals.png", "badge_icon_url": "https://cdn.fetchrewards.com/images/episode-badge.png", "is_unread": true, "cta": { "label": "See Deals", "action": "deeplink://offers/daily" }, "primary_action": "deeplink://episode/968aed30-a619-4ac5-a00d-f4a3a581e9cd" } ] }, { "header": "Yesterday", "episodes": [ { "id": "dad43180-c301-41d9-a37d-97486f2679af", "title": "Snack Suggestions", "display_time": "1d", "image_url": "https://cdn.fetchrewards.com/images/episode-default-hero.png", "icon_url": "https://cdn.fetchrewards.com/images/episode-default-icon.png", "is_unread": false, "cta": { "label": "View", "action": "deeplink://episode/dad43180-c301-41d9-a37d-97486f2679af" }, "primary_action": "deeplink://episode/dad43180-c301-41d9-a37d-97486f2679af" } ] } ], "pagination": { "next_cursor": "eyJVc2VySWQiOi...", "has_more": true }}Field Definitions
Section titled “Field Definitions”BFF Response (top-level):
| Field | Type | Description |
|---|---|---|
unread_count | int | Total unread episodes (same value regardless of pagination) |
sections | BffSection[] | Time-based sections containing episodes. Empty sections are omitted. Order: Today → Yesterday → In the past. |
pagination | BffPagination | Pagination info |
BffSection:
| Field | Type | Description |
|---|---|---|
header | string | Section header: "Today", "Yesterday", or "In the past" |
episodes | BffEpisode[] | Episodes in this section (preserves sort order from consumer-agent) |
Section Classification:
| displayDate pattern | Section |
|---|---|
| ”Just now” | Today |
| ”X minute(s) ago” | Today |
| ”X hour(s) ago” | Today |
| ”Yesterday” | Yesterday |
| ”1 day ago” | Yesterday |
| ”X days ago” (X > 1) | In the past |
| Absolute dates (“Jan 15”) | In the past |
BffEpisode:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | yes | Episode UUID |
title | string | yes | Episode title |
subtitle | string | no | Subtitle text. Omitted if not set. |
display_time | string | yes | Compact timestamp: "now", "5m", "3h", "1d", "Jan 15" |
image_url | string | yes | Hero image URL. Falls back to default: episode-default-hero.png |
icon_url | string | yes | Icon URL. Falls back to default: episode-default-icon.png |
badge_icon_url | string | no | Badge overlay icon. Only present when image_url or icon_url was explicitly set (not defaulted). |
is_unread | bool | yes | Whether the episode has been opened |
cta | CTA | yes | Call-to-action button |
primary_action | string | yes | Always deeplink://episode/{id} |
CTA:
| Field | Type | Description |
|---|---|---|
label | string | Button text. Falls back to "View" if not set. |
action | string | Deeplink URL. Falls back to deeplink://episode/{id} if not set. |
BffPagination:
| Field | Type | Description |
|---|---|---|
next_cursor | string | null | Opaque cursor for next page |
has_more | bool | true if more pages are available |
Display Time Compaction
Section titled “Display Time Compaction”The BFF transforms consumer-agent’s relative timestamps into compact mobile format:
| Input | Output |
|---|---|
| ”Just now” | "now" |
| ”5 minutes ago” | "5m" |
| ”1 minute ago” | "1m" |
| ”3 hours ago” | "3h" |
| ”1 hour ago” | "1h" |
| ”Yesterday” | "1d" |
| ”4 days ago” | "4d" |
| ”Jan 15” | "Jan 15" (passthrough) |
Default Values
Section titled “Default Values”When episodes are created without explicit BFF fields (e.g., user-initiated conversations), the BFF applies defaults:
| Field | Default Value |
|---|---|
image_url | https://cdn.fetchrewards.com/images/episode-default-hero.png |
icon_url | https://cdn.fetchrewards.com/images/episode-default-icon.png |
badge_icon_url | omitted (only set for episodes with custom image/icon) |
cta.label | "View" |
cta.action | deeplink://episode/{id} |
Client Behavior
Section titled “Client Behavior”- Render sections in array order (Today → Yesterday → In the past). Empty sections are omitted from the response.
- Use section
headeras a section divider label in the feed UI. - Render episodes within each section in array order.
- Use
has_moreto show/hide “load more” UI. - Use
unread_countfor badge display. primary_actionis the deeplink to open the episode transcript.cta.actionmay differ fromprimary_actionfor episodes with custom CTAs (e.g.,deeplink://offers/daily).
GET /api/v1/episodes/bff/unread
Section titled “GET /api/v1/episodes/bff/unread”Same as GET /api/v1/episodes/bff, but filtered to unread episodes only. Uses the same response shape with sections.
Query Parameters
Section titled “Query Parameters”Same as /api/v1/episodes/bff.
Response — 200 OK
Section titled “Response — 200 OK”Same shape as /api/v1/episodes/bff. Only episodes where is_unread=true are included. Sections that become empty after filtering are omitted.
Error Responses
Section titled “Error Responses”401 Unauthorized
Section titled “401 Unauthorized”Missing or invalid authentication credentials.
{ "error": "Unauthorized"}503 Service Unavailable
Section titled “503 Service Unavailable”Python agent (consumer-agent) is not configured.
Episodes endpoints require Python agent configuration502 Bad Gateway
Section titled “502 Bad Gateway”Consumer-agent is unreachable or returned an unparseable response.
Failed to proxy request to Python agentNon-200 Passthrough
Section titled “Non-200 Passthrough”If consumer-agent returns a non-200 status (e.g., 400, 404, 422), the response is passed through to the client unchanged.
Compatibility
Section titled “Compatibility”- Fields unknown to the client must be ignored.
- New optional fields may be added to
BffEpisodeorBffSectionwithout version bump. - The
sectionsandepisodesarrays may contain new fields without notice. - Breaking changes to required fields will use a new API version path.