Emotional AI-Powered Personalization Platform Version: 2.0.0 Last Updated: 2025-12-04 Base URL: https://api.tasteray.com
TasteRay Emotional API is an Emotional AI-powered personalization platform. It understands not just WHAT users want, but WHY they want it - creating recommendations that truly connect. Perfect for building "For You" feeds, personalized tabs, and smart carousels.
All API requests (except /v1/health) require authentication using an API key in the request header.
X-API-Key: reco_live_your_api_key_here
reco_test_* - For development and testingreco_live_* - For production use401 UnauthorizedRate limits are enforced per API key using a sliding window algorithm (1-minute windows).
| Tier | Requests/Month | Requests/Minute | Typical Use Case |
|---|---|---|---|
| Free | 1,000 | 5 | Testing, demos |
| Basic | 10,000 | 50 | Small apps |
| Pro | 100,000 | 200 | Production apps |
| Enterprise | Unlimited | Custom | High-scale apps |
Every response includes rate limit information:
X-RateLimit-Limit: 50
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1699123456
X-RateLimit-Tier: pro
When you exceed your rate limit:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded for your tier",
"retry_after": 60
}
}
Response Status: 429 Too Many Requests
Best Practice: Implement exponential backoff:
Wait time = min(base_delay * (2 ^ attempt), max_delay)
Generate personalized recommendations for any vertical.
Endpoint: POST https://api.tasteray.com/v1/recommend
Content-Type: application/json
X-API-Key: reco_live_your_api_key_here
{
"vertical": "string (required)",
"context": {
"preferences": ["string (conditional, required if profile not provided)"],
"profile": "string (conditional, free-form user profile text)",
"constraints": {
"key": "value (optional)"
},
"history": [
{
"item": "string (required)",
"rating": "string (required)",
"metadata": {}
}
],
"history_text": "string (optional, free-form history text)"
},
"items": [
{
"id": "string (required)",
"name": "string (required)",
"description": "string (optional)",
"metadata": {}
}
],
"options": {
"count": "number (1-10, default: 3)",
"include_alternatives": "boolean (default: true)",
"explanation_depth": "string (brief|detailed, default: detailed)",
"language": "string (locale code, default: en_US)",
"grounding": "boolean (enable web search, default: true when no items, false with items)",
"fast": "boolean (enable fast inference, default: false)"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
vertical |
string | Yes | Vertical type (e.g., movies, restaurants, products, travel) |
context.preferences |
string[] | Conditional | User preferences (min 1, max 20). Required if profile not provided |
context.profile |
string | Conditional | Free-form text describing user profile/preferences. Required if preferences not provided |
context.constraints |
object | No | Vertical-specific constraints (budget, location, etc.) |
context.history |
array | No | Previous items with ratings (max 50 items) |
context.history[].item |
string | Yes | Name/identifier of item |
context.history[].rating |
string | Yes | User's rating (flexible format) |
context.history[].metadata |
object | No | Additional context |
context.history_text |
string | No | Free-form text describing user history (alternative to structured history) |
items |
array | No | Specific items to choose from |
items[].id |
string | Yes | Unique identifier |
items[].name |
string | Yes | Item name |
items[].description |
string | No | Item description |
items[].metadata |
object | No | Additional metadata |
options.count |
number | No | Number of recommendations (1-10, default: 3) |
options.include_alternatives |
boolean | No | Include alternatives (default: true) |
options.explanation_depth |
string | No | brief or detailed (default: detailed) |
options.language |
string | No | Locale code for response language (e.g., en_US, es_ES, fr_FR, default: en_US) |
options.grounding |
boolean | No | Enable web search for real-time information (default: true when no items provided, false when items are provided) |
options.fast |
boolean | No | Enable fast inference for faster responses (default: false) |
Status: 200 OK
{
"recommendations": [
{
"item": {
"name": "The Matrix",
"vertical": "entertainment",
"type": "movie",
"metadata": {
"year": 1999,
"runtime": 136,
"genre": ["Sci-Fi", "Action"],
"director": "Lana Wachowski, Lilly Wachowski",
"rating": 8.7
}
},
"explanation": {
"why_match": "The film that redefined sci-fi. Still unmatched.",
"key_factors": [
"Questions that linger. What is real? What is control?",
"Action that changed cinema. Groundbreaking choreography.",
"136 minutes that fly by. Every scene earns its place."
],
"potential_concerns": [
"Intense action sequences. Not for the faint of heart.",
"Demands your attention. The philosophy rewards focus."
]
},
"confidence": 0.92,
"metadata": {
"taste_match_score": 92,
"popularity_rank": 5,
"recency": "classic"
}
}
],
"meta": {
"request_id": "req_1699123456789",
"timestamp": "2025-11-04T12:00:00Z",
"processing_time_ms": 1243,
"prompt_version": "v2.1",
"total_results": 3,
"tool_usage": {
"total_tool_calls": 2,
"tools_used": ["webSearch"],
"tools_called": [
{
"tool_name": "webSearch",
"arguments": {
"query": "best sci-fi movies 2024 with philosophical themes",
"numResults": 10,
"type": "auto"
},
"timestamp": "2025-11-04T12:00:01Z"
},
{
"tool_name": "webSearch",
"arguments": {
"query": "mind-bending action sci-fi films",
"numResults": 10,
"type": "auto"
},
"timestamp": "2025-11-04T12:00:03Z"
}
]
}
}
}
Generate detailed explanation for why a specific item matches user preferences.
Endpoint: POST https://api.tasteray.com/v1/explain
{
"vertical": "string (required)",
"item": {
"name": "string (required)",
"type": "string (required)",
"metadata": {}
},
"context": {
"user_preferences": ["string (conditional, required if profile not provided)"],
"profile": "string (conditional, free-form user profile text)",
"constraints": {}
},
"options": {
"depth": "string (brief|detailed, default: detailed)",
"include_alternatives": "boolean (default: false)",
"language": "string (locale code, default: en_US)",
"grounding": "boolean (enable web search, default: true)",
"fast": "boolean (enable fast inference, default: false)"
}
}
Status: 200 OK
{
"explanation": {
"summary": "Your sanctuary. Anywhere you go.",
"detailed_reasoning": {
"taste_alignment": [
"The world fades away. 30dB noise cancellation.",
"Days of listening. 30-hour battery."
],
"constraint_satisfaction": [
"Flagship quality at $399. Right at your budget.",
"Travel-ready by design. Quick-charge, foldable."
],
"unique_factors": [
"Travelers trust them. 91% recommend for commuting."
]
},
"potential_concerns": [
"Over-ear, not in-ear. The trade-off for comfort."
],
"alternatives": []
},
"confidence": 0.95,
"meta": {
"request_id": "req_1699123456790",
"timestamp": "2025-11-04T12:05:00Z",
"processing_time_ms": 876,
"tool_usage": {
"total_tool_calls": 1,
"tools_used": ["webSearch"],
"tools_called": [
{
"tool_name": "webSearch",
"arguments": {
"query": "Sony WH-1000XM5 reviews 2024 noise cancelling",
"numResults": 10,
"type": "auto"
},
"timestamp": "2025-11-04T12:05:01Z"
}
]
}
}
}
Check API health status and service availability.
Endpoint: GET https://api.tasteray.com/v1/health
Authentication: Not required
Status: 200 OK (Healthy) or 503 Service Unavailable (Unhealthy)
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2025-11-04T12:00:00Z",
"checks": {
"ai_service": "ok",
"langfuse": "ok",
"kv_store": "ok"
}
}
Retrieve usage statistics for your API key.
Endpoint: GET https://api.tasteray.com/v1/usage
Status: 200 OK
{
"period": "current_month",
"start_date": "2025-11-01T00:00:00Z",
"end_date": "2025-11-30T23:59:59Z",
"usage": {
"requests_made": 2450,
"requests_limit": 10000,
"requests_remaining": 7550,
"percentage_used": 24.5,
"reset_date": "2025-12-01T00:00:00Z"
},
"breakdown_by_vertical": {
"entertainment": 1200,
"restaurant": 800,
"product": 350,
"travel": 100
},
"breakdown_by_day": [
{"date": "2025-11-01", "requests": 120},
{"date": "2025-11-02", "requests": 135}
],
"tier": {
"name": "Basic",
"rate_limit": "50 requests/minute"
}
}
The API is vertical-agnostic and supports any domain. Common verticals include:
Entertainment: movies, tv-series, books, music, podcasts, games, mobile-apps
Food & Dining: restaurants, cafes, bars, recipes, meal-kits
Travel: hotels, flights, destinations, activities, tours, cruises
Products: electronics, clothing, furniture, home-goods, beauty, sports
Services: courses, jobs, real-estate, healthcare, finance, fitness
Events: concerts, conferences, sports-events, theater, festivals
And many more: The API adapts to any vertical you specify.
All verticals share a common context structure. You can provide structured data OR free-form text:
interface RecommendationContext {
// Provide EITHER structured preferences OR free-form profile (or both)
preferences?: string[] // User preferences (e.g., ["sci-fi", "action"])
profile?: string // Free-form text describing user profile/preferences
constraints?: Record<string, any> // Any constraints (budget, location, features)
// Provide EITHER structured history OR free-form history_text (or both)
history?: Array<{
item: string // Item name or identifier
rating: string // Flexible rating (e.g., "liked", "5 stars", "8/10")
metadata?: Record<string, any> // Optional context
}>
history_text?: string // Free-form text describing user history
}
Flexibility: The API accepts either structured arrays or unstructured text. This makes it easy to:
{
"preferences": ["sci-fi", "thriller", "thought-provoking"],
"constraints": {
"runtime_max": 150,
"release_year_min": 2010,
"platforms": ["Netflix", "HBO"]
},
"history": [
{
"item": "Inception",
"rating": "loved",
"metadata": {"year": 2010}
}
]
}
{
"preferences": ["Italian", "romantic", "authentic"],
"constraints": {
"location": "Seattle, WA",
"price_range": "$$$",
"dietary_restrictions": ["gluten-free options"]
},
"history": [
{
"item": "Canlis",
"rating": "excellent",
"metadata": {"occasion": "anniversary"}
}
]
}
{
"preferences": ["wireless", "noise-cancelling", "long battery"],
"constraints": {
"category": "headphones",
"budget_max": 300,
"brand_preference": ["Sony", "Bose"]
},
"history": [
{
"item": "Sony WH-1000XM4",
"rating": "9/10",
"metadata": {"owned_for": "2 years"}
}
]
}
{
"preferences": ["beaches", "adventure", "culture"],
"constraints": {
"budget_total": 3000,
"duration_days": 14,
"region": "Southeast Asia",
"season": "winter"
},
"history": [
{
"item": "Bali, Indonesia",
"rating": "loved",
"metadata": {"visited": "2023"}
}
]
}
While the API is vertical-agnostic, here are best practices for common verticals:
Preferences: Genres, themes, mood, pacing Constraints: Runtime, release year, platforms, content rating History: Previously watched/read items with ratings
Preferences: Cuisine type, atmosphere, dietary focus Constraints: Location, price range, dietary restrictions, party size History: Past dining experiences with ratings
Preferences: Activity type, atmosphere, cultural interests Constraints: Budget, duration, location, season, accessibility History: Past trips with ratings and notes
Preferences: Features, style, brand preferences Constraints: Budget, category, specifications, compatibility History: Owned products with ratings and usage notes
Preferences: Skills to learn, career goals, specializations Constraints: Budget, time commitment, location, qualifications History: Past experiences with ratings
TasteRay is designed for building personalized surfaces like "For You" tabs, feeds, and carousels.
Create dynamic, personalized content feeds:
// Building a "For You" movie feed
const response = await fetch('https://api.tasteray.com/v1/recommend', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': apiKey
},
body: JSON.stringify({
vertical: 'movies',
context: {
preferences: userProfile.preferences,
history: userProfile.recentWatches.slice(0, 20)
},
options: {
count: 20,
explanation_depth: 'brief'
}
})
});
// Sort by taste_match_score for optimal ranking
const ranked = response.recommendations
.sort((a, b) => b.metadata.taste_match_score - a.metadata.taste_match_score);
Re-rank existing content based on user profiles:
// Personalize a category page
const response = await fetch('https://api.tasteray.com/v1/recommend', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': apiKey },
body: JSON.stringify({
vertical: 'products',
items: categoryProducts, // Your existing product list
context: { preferences: userProfile.preferences },
options: { count: categoryProducts.length }
})
});
// Items now ranked by personal relevance
| Field | Use Case |
|---|---|
metadata.taste_match_score |
Primary ranking signal (0-100) |
metadata.mood_match |
Filter by mood context |
confidence |
Secondary ranking signal (0.0-1.0) |
explanation.why_match |
"Why you'll love it" tooltip |
explanation.key_factors |
Expandable details section |
All successful API responses include a meta object with request metadata and optional tool usage information.
interface ResponseMeta {
request_id: string; // Unique identifier for the request
timestamp: string; // ISO 8601 timestamp of the response
processing_time_ms?: number; // Time taken to process the request (milliseconds)
model?: string; // AI model identifier used
prompt_version?: string; // Version of the prompt template used
total_results?: number; // Number of recommendations returned
tool_usage?: ToolUsageInfo; // Information about tools/searches used (optional)
}
When the grounding option is enabled (default for most requests), the API may use web search tools to gather real-time information. The tool_usage field provides full transparency about these searches:
interface ToolUsageInfo {
total_tool_calls: number; // Total number of tool calls made
tools_used: string[]; // Names of tools used (e.g., ["webSearch"])
tools_called?: ToolCallMetadata[]; // Details of each tool call (optional)
}
interface ToolCallMetadata {
tool_name: string; // Name of the tool that was called (e.g., "webSearch")
arguments: Record<string, any>; // Arguments passed to the tool
timestamp: string; // ISO 8601 timestamp of when the tool was called
}
{
"meta": {
"request_id": "req_1699123456789",
"timestamp": "2025-11-04T12:00:00Z",
"processing_time_ms": 2341,
"prompt_version": "v2.1",
"total_results": 3,
"tool_usage": {
"total_tool_calls": 2,
"tools_used": ["webSearch"],
"tools_called": [
{
"tool_name": "webSearch",
"arguments": {
"query": "best Italian restaurants San Francisco 2024",
"numResults": 10,
"type": "auto"
},
"timestamp": "2025-11-04T12:00:01Z"
},
{
"tool_name": "webSearch",
"arguments": {
"query": "newly opened restaurants North Beach San Francisco",
"numResults": 10,
"type": "auto"
},
"timestamp": "2025-11-04T12:00:03Z"
}
]
}
}
}
options.grounding is true (default for most requests)options.grounding is false or when items are provided without explicit grounding enablementAll errors follow this structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": "Additional context about the error",
"suggestion": "How to fix the error",
"documentation_url": "/docs#error-handling"
},
"meta": {
"request_id": "req_error_123",
"timestamp": "2025-11-04T12:10:00Z"
}
}
| Code | Description | Solution |
|---|---|---|
INVALID_VERTICAL |
Unsupported vertical | Check supported verticals |
MISSING_CONTEXT |
Required field missing | Add required fields |
INVALID_FORMAT |
Invalid JSON | Validate JSON syntax |
VALIDATION_ERROR |
Schema validation failed | Check request schema |
| Code | Description | Solution |
|---|---|---|
INVALID_API_KEY |
Invalid or revoked API key | Check/regenerate key |
MISSING_API_KEY |
No API key provided | Add X-API-Key header |
| Code | Description | Solution |
|---|---|---|
INSUFFICIENT_PERMISSIONS |
No access to vertical | Upgrade plan |
QUOTA_EXCEEDED |
Monthly quota exceeded | Upgrade plan or wait |
| Code | Description | Solution |
|---|---|---|
RATE_LIMIT_EXCEEDED |
Rate limit exceeded | Wait or upgrade plan |
| Code | Description | Solution |
|---|---|---|
INTERNAL_ERROR |
Server error | Retry after delay |
AI_SERVICE_ERROR |
AI service error | Retry after delay |
PROMPT_ERROR |
Langfuse error | Contact support |
| Code | Description | Solution |
|---|---|---|
SERVICE_UNAVAILABLE |
Upstream service down | Retry with backoff |
| Code | Description | Solution |
|---|---|---|
TIMEOUT |
Request timeout | Simplify request |
const axios = require('axios');
async function getRecommendations() {
try {
const response = await axios.post(
'https://api.tasteray.com/v1/recommend',
{
vertical: 'entertainment',
context: {
preferences: ['sci-fi', 'action', 'thought-provoking'],
constraints: {
runtime_max: 150
}
},
options: {
count: 3,
explanation_depth: 'detailed'
}
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.TASTERAY_API_KEY
}
}
);
console.log('Recommendations:', response.data.recommendations);
return response.data;
} catch (error) {
if (error.response) {
console.error('Error:', error.response.data.error);
} else {
console.error('Request failed:', error.message);
}
}
}
import requests
import os
def get_recommendations():
url = 'https://api.tasteray.com/v1/recommend'
headers = {
'Content-Type': 'application/json',
'X-API-Key': os.environ.get('TASTERAY_API_KEY')
}
payload = {
'vertical': 'restaurant',
'context': {
'preferences': ['Italian', 'romantic', 'authentic'],
'constraints': {
'location': 'Seattle, WA',
'price_range': '$$$'
}
},
'options': {
'count': 3,
'explanation_depth': 'detailed'
}
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
for rec in data['recommendations']:
print(f"\n{rec['item']['name']}")
print(f"Confidence: {rec['confidence'] * 100}%")
print(f"Why: {rec['explanation']['why_match']}")
return data
except requests.exceptions.HTTPError as e:
print(f"Error: {e.response.json()['error']}")
# Basic recommendation request
curl -X POST https://api.tasteray.com/v1/recommend \
-H "Content-Type: application/json" \
-H "X-API-Key: reco_live_your_api_key" \
-d '{
"vertical": "travel",
"context": {
"preferences": ["beaches", "adventure", "culture"],
"constraints": {
"budget_total": 3000,
"duration_days": 14
}
},
"options": {
"count": 5
}
}'
# Explain endpoint
curl -X POST https://api.tasteray.com/v1/explain \
-H "Content-Type: application/json" \
-H "X-API-Key: reco_live_your_api_key" \
-d '{
"vertical": "product",
"item": {
"name": "Sony WH-1000XM5",
"type": "headphones"
},
"context": {
"user_preferences": ["noise-cancelling", "wireless"]
}
}'
# Check usage
curl https://api.tasteray.com/v1/usage \
-H "X-API-Key: reco_live_your_api_key"
# Health check
curl https://api.tasteray.com/v1/health
# Spanish recommendations
curl -X POST https://api.tasteray.com/v1/recommend \
-H "Content-Type: application/json" \
-H "X-API-Key: reco_live_your_api_key" \
-d '{
"vertical": "entertainment",
"context": {
"preferences": ["sci-fi", "action"]
},
"options": {
"count": 3,
"language": "es_ES"
}
}'
# French explanation
curl -X POST https://api.tasteray.com/v1/explain \
-H "Content-Type: application/json" \
-H "X-API-Key: reco_live_your_api_key" \
-d '{
"vertical": "restaurants",
"item": {
"name": "Le Bernardin",
"type": "fine_dining"
},
"context": {
"user_preferences": ["seafood", "elegant"]
},
"options": {
"language": "fr_FR"
}
}'
# Japanese recommendations
curl -X POST https://api.tasteray.com/v1/recommend \
-H "Content-Type: application/json" \
-H "X-API-Key: reco_live_your_api_key" \
-d '{
"vertical": "travel",
"context": {
"preferences": ["temples", "nature", "hot springs"]
},
"options": {
"count": 5,
"language": "ja_JP"
}
}'
Note: The language parameter accepts any valid locale code (e.g., en_US, es_ES, fr_FR, de_DE, ja_JP, zh_CN, pt_BR, it_IT). All AI-generated explanations and text will be in the specified language, while error messages remain in English.
Keep Context Focused:
Specify Constraints Clearly:
// Good
{"budget_max": 300, "category": "headphones"}
// Too vague
{"budget": "not too expensive"}
Use Appropriate Count:
Implement Retry Logic:
async function requestWithRetry(url, data, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await axios.post(url, data);
} catch (error) {
if (error.response?.status === 429) {
const retryAfter = error.response.headers['retry-after'];
await sleep(retryAfter * 1000);
} else if (error.response?.status >= 500) {
await sleep(Math.pow(2, i) * 1000); // Exponential backoff
} else {
throw error; // Don't retry client errors
}
}
}
}
Check Rate Limits Proactively:
if (response.headers['x-ratelimit-remaining'] < 5) {
console.warn('Approaching rate limit!');
// Consider throttling requests
}
Use Structured Output:
Cache Recommendations:
Batch Requests When Possible:
countProtect API Keys:
Validate User Input:
Track Usage:
// Periodically check usage
const usage = await checkUsage();
if (usage.percentage_used > 80) {
notifyAdmin('Approaching quota limit');
}
Log Request IDs:
Test with Various Inputs:
Use Test Keys:
reco_test_* keys for developmentreco_live_* only for productionThe API is completely stateless:
Deployed on Cloudflare Workers:
Uses Cloudflare KV for:
Note: KV is eventually consistent (~60s propagation time)
All notable changes to the TasteRay Emotional API are documented below. The format is based on Keep a Changelog.
Added
Changed
Added
Changed
Added
Changed
Removed
Added
Changed
Changed
Added
Version Summary
End of Documentation