Emotional AI-Powered Personalization Platform Version: 0.7.0 Last Updated: 2026-03-21 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)
TasteRay offers two agent skills built on the Agent Skills open standard. Install them into any compatible coding agent to add preference elicitation and personalized recommendations directly into your workflow.
Guides users through a natural conversation to uncover what they truly want — mood, constraints, past favorites — so TasteRay can deliver spot-on recommendations.
npx skills add tasteray/skills/elicitation
Calls the TasteRay API to generate emotionally intelligent recommendations — complete with match scores and human-readable explanations your users can trust.
npx skills add tasteray/skills/recommendations
npx skills add tasteray/skills
Claude Code, Cursor, Copilot, Windsurf, Cline, Aider, Roo Code
All notable changes to the TasteRay Emotional API are documented below. The format is based on Keep a Changelog.
Changed
Added
Added
Changed
Added
Changed
Added
Changed
Added
Changed
Added
Changed
Added
Version Summary
End of Documentation