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

ParameterTypeRequiredDescription
user_idstringYesHashed user identifier (SHA-256, 64 chars)
tenant_idstringYesTenant identifier (host app)
limitintegerNoNumber of videos (default: 20, max: 100)
demographicsJSONNoOptional demographic hints (age_group, interests)
offsetintegerNoPagination 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

ParameterTypeRequiredDescription
tenant_idstringYesTenant identifier
limitintegerNoNumber 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

400

Bad Request

Missing required parameters or invalid parameter values.

{
  "error": "Bad Request",
  "message": "Missing required parameter: user_id",
  "code": "MISSING_PARAMETER"
}
404

Not Found

Tenant ID not found or user has no accessible content.

{
  "error": "Not Found",
  "message": "Tenant not found: tenant999",
  "code": "TENANT_NOT_FOUND"
}
429

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
}
500

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
}
503

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.

HeaderValuePurpose
Cache-Controlmax-age=60, privateCache for 60s, user-specific
ETag"06d6cbdcfc2..."Conditional requests
VaryAccept-EncodingVary by encoding
X-Cache-StatusHIT | MISSDebug cache performance
X-Response-Time142msMonitor 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