API Contract
Endpoint specifications, request/response schemas, and error handling
GET /v1/feed
Primary endpoint for fetching personalized video feeds. Returns a ranked list of videos tailored to the user's viewing history and engagement patterns.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| user_id | string | Yes | Hashed user identifier (SHA-256, 64 chars) |
| tenant_id | string | Yes | Tenant identifier (host app) |
| limit | integer | No | Number of videos (default: 20, max: 100) |
| demographics | JSON | No | Optional demographic hints (age_group, interests) |
| offset | integer | No | Pagination offset (default: 0) |
Example Request
curl -X GET "https://api.storyteller.com/v1/feed?user_id=06d6cbdcfc221d2f4460c17193442b9db221f30950f1c17af4e73e6e1788002b&tenant_id=tenant1&limit=20" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"Success Response (200 OK)
{
"user_id": "06d6cbdcfc221d2f4460c17193442b9db221f30950f1c17af4e73e6e1788002b",
"tenant_id": "tenant1",
"personalized": true,
"feed": [
{
"video_id": "vid_9k3m2l1a",
"title": "Advanced Gaming Strategies",
"thumbnail_url": "https://cdn.storyteller.com/thumbs/vid_9k3m2l1a.jpg",
"duration_seconds": 180,
"category": "gaming",
"score": 0.89,
"editorial_boost": 1.2,
"created_at": "2026-01-10T14:30:00Z",
"metadata": {
"creator": "ProGamer123",
"views": 125000,
"likes": 8500
},
"ranking_reason": "High watch history match + editorial boost"
},
{
"video_id": "vid_7j8k5n2b",
"title": "eSports Tournament Highlights",
"thumbnail_url": "https://cdn.storyteller.com/thumbs/vid_7j8k5n2b.jpg",
"duration_seconds": 240,
"category": "gaming",
"score": 0.82,
"editorial_boost": 1.0,
"created_at": "2026-01-11T09:15:00Z",
"metadata": {
"creator": "ESportsDaily",
"views": 250000,
"likes": 15000
},
"ranking_reason": "Category affinity match"
}
],
"metadata": {
"total_candidates": 1247,
"response_time_ms": 142,
"cache_hit": false,
"algorithm_version": "v1.2.0",
"ranking_weights": {
"watch_history": 0.5,
"engagement": 0.3,
"editorial": 0.2
}
},
"pagination": {
"limit": 20,
"offset": 0,
"has_more": true
}
}GET /v1/feed/non-personalized
Fallback endpoint that returns editorial-curated content without personalization. Used when feature flag is disabled or as a kill switch fallback.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| tenant_id | string | Yes | Tenant identifier |
| limit | integer | No | Number of videos (default: 20) |
Example Response
{
"tenant_id": "tenant1",
"personalized": false,
"feed": [
{
"video_id": "vid_1a2b3c4d",
"title": "Editor's Pick: Top Video Today",
"score": 1.5,
"editorial_boost": 1.5
}
],
"metadata": {
"response_time_ms": 45,
"cache_hit": true,
"algorithm_version": "editorial_only"
}
}Error Responses
Bad Request
Missing required parameters or invalid parameter values.
{
"error": "Bad Request",
"message": "Missing required parameter: user_id",
"code": "MISSING_PARAMETER"
}Not Found
Tenant ID not found or user has no accessible content.
{
"error": "Not Found",
"message": "Tenant not found: tenant999",
"code": "TENANT_NOT_FOUND"
}Too Many Requests
Rate limit exceeded (more than 3000 RPS).
{
"error": "Too Many Requests",
"message": "Rate limit exceeded. Try again in 60 seconds.",
"code": "RATE_LIMIT_EXCEEDED",
"retry_after_seconds": 60
}Internal Server Error
Unexpected server error. System will automatically fall back to non-personalized feed.
{
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"code": "INTERNAL_ERROR",
"fallback_available": true
}Service Unavailable
Service temporarily degraded. Kill switch activated or maintenance mode.
{
"error": "Service Unavailable",
"message": "Personalization service degraded. Using fallback.",
"code": "SERVICE_DEGRADED",
"fallback_enabled": true
}Caching Headers
Responses include standard HTTP caching headers to enable CDN and client-side caching.
Note: These HTTP caching headers are separate from Redis, which is used for server-side application caching. HTTP headers control caching by CDNs, reverse proxies, and browsers downstream from the API, while Redis caches computed feed responses and data structures within the application layer to reduce database queries. The two complement each other: Redis reduces load on the database and application servers, while HTTP headers reduce load on the API by enabling edge caching. For more details on how Redis is used in the architecture, see the Architecture page.
| Header | Value | Purpose |
|---|---|---|
| Cache-Control | max-age=60, private | Cache for 60s, user-specific |
| ETag | "06d6cbdcfc2..." | Conditional requests |
| Vary | Accept-Encoding | Vary by encoding |
| X-Cache-Status | HIT | MISS | Debug cache performance |
| X-Response-Time | 142ms | Monitor latency |
Rate Limiting
Rate limits are enforced per tenant to ensure fair usage and system stability.
Per-Tenant Limits
- • 3,000 requests per second (peak burst)
- • 600 requests per second (sustained average)
- • Exceeded requests return 429 status code
- • Rate limit resets every 60 seconds
Response Headers
X-RateLimit-Limit: 3000
X-RateLimit-Remaining: 2847
X-RateLimit-Reset: 1704985200