Skip to main content
This tutorial shows you how to build a smart resource recommender using the Gloo AI Recommendations API. You’ll learn to surface publisher-scoped content recommendations, add snippet previews for richer displays, and discover resources from across the Gloo affiliate publisher network. Unlike keyword search, the Recommendations API ranks content by semantic relevance to a user’s query, making it ideal for “You might also like” panels, related-resource sections, and cross-publisher content discovery.
The Recommendations API returns ranked content items from your publisher’s collection or the Gloo affiliate network. It is optimized for recommendation UI patterns, not open-ended search.

Prerequisites

Before starting, ensure you have:

Working Code Sample

Follow along with complete working examples in all 6 languages (JavaScript, TypeScript, Python, PHP, Go, Java). Includes a proxy server and browser-based frontend for each language.Setup and testing instructions are provided later.
The code snippets in this tutorial are simplified and self-contained — designed for readability and easy copy-paste. The cookbook examples use a modular architecture plus production niceties. Both implement the same APIs and patterns.

Understanding the Recommendations API

Three endpoints cover the main recommendation use cases:
EndpointPurpose
POST /ai/v1/data/items/recommendations/basePublisher-scoped ranked items — metadata only
POST /ai/v1/data/items/recommendations/verbosePublisher-scoped ranked items — includes full snippet text
POST /ai/v1/data/affiliates/referenced-itemsCross-publisher discovery from the Gloo affiliate network

Publisher Endpoints (base & verbose)

Both publisher endpoints share the same required parameters:
ParameterDescription
queryThe topic or question to match content against
item_countNumber of items to return (1–50)
certainty_thresholdMinimum relevance score (0–1). Default: 0.75
collectionAlways "GlooProd"
tenantYour publisher (tenant) name

Affiliate Endpoint

The affiliate endpoint searches across the full Gloo publisher network so no collection or tenant is required:
ParameterDescription
queryThe topic or question to match content against
item_countNumber of items to return (1–50)
certainty_thresholdMinimum relevance score. Default: 0.75

Response Structure

Publisher endpoints return an array of items. Each item includes a uuids array with the matched snippet and its relevance metadata:
[
  {
    "item_id": "abc123",
    "item_title": "Finding True Happiness",
    "author": ["John Smith"],
    "item_url": "https://example.com/finding-true-happiness",
    "uuids": [
      {
        "uuid": "snippet-uuid",
        "ai_title": "The Path to Contentment",
        "ai_subtitle": "A practical guide",
        "item_summary": "Explores the foundations of lasting happiness...",
        "certainty": 0.89,
        "snippet": "Full snippet text here (verbose endpoint only)"
      }
    ]
  }
]
Key fields:
  • uuids[0].certainty — Relevance score (0–1, higher = more relevant)
  • uuids[0].ai_title — AI-generated section title for the matched snippet
  • uuids[0].item_summary — AI-generated summary of the item
  • uuids[0].snippet — Full snippet text (verbose endpoint only)
The affiliate endpoint returns a flatter structure without uuids, adding tradition and item_subtitle fields instead.

Step 1: Publisher Recommendations

Let’s fetch ranked content from your publisher’s collection. The base endpoint returns metadata only with no snippet text, making it ideal for clean list UIs.
import requests, os, sys, time
from dotenv import load_dotenv
load_dotenv()

CLIENT_ID     = os.getenv("GLOO_CLIENT_ID", "YOUR_CLIENT_ID")
CLIENT_SECRET = os.getenv("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET")
TENANT        = os.getenv("GLOO_TENANT", "your-tenant-name")
COLLECTION    = os.getenv("GLOO_COLLECTION", "GlooProd")
TOKEN_URL     = "https://platform.ai.gloo.com/oauth2/token"
BASE_URL      = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/base"

token_info = {}

def ensure_token():
    if not token_info or time.time() > token_info.get("expires_at", 0) - 60:
        r = requests.post(TOKEN_URL, data={"grant_type": "client_credentials"},
                          auth=(CLIENT_ID, CLIENT_SECRET), timeout=30)
        r.raise_for_status()
        d = r.json()
        d["expires_at"] = int(time.time()) + d["expires_in"]
        token_info.update(d)
    return token_info["access_token"]

def get_recommendations(query, item_count=5):
    token = ensure_token()
    payload = {
        "query": query,
        "item_count": item_count,
        "certainty_threshold": 0.75,
        "collection": COLLECTION,
        "tenant": TENANT,
    }
    r = requests.post(
        BASE_URL,
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json=payload,
        timeout=60,
    )
    r.raise_for_status()
    return r.json()

query      = sys.argv[1] if len(sys.argv) > 1 else "How do I deal with anxiety?"
item_count = int(sys.argv[2]) if len(sys.argv) > 2 else 5

items = get_recommendations(query, item_count)
print(f"Found {len(items)} item(s):\n")
for i, item in enumerate(items, 1):
    print(f"--- Item {i} ---")
    print(f"Title:  {item.get('item_title', 'N/A')}")
    if item.get("author"):
        print(f"Author: {', '.join(item['author'])}")
    if item.get("uuids"):
        top = item["uuids"][0]
        print(f"Relevance: {top.get('certainty', 0):.0%}")
        if top.get("ai_title"):     print(f"Section:   {top['ai_title']}")
        if top.get("item_summary"): print(f"Summary:   {top['item_summary']}")
    print()

What You’ll See

A successful call returns up to 5 items with metadata such as the title, summary, and relevance score:
Found 3 item(s):

--- Item 1 ---
Title:  Finding True Happiness
Author: John Smith
Relevance: 89%
Section:   The Path to Contentment
Summary:   This article explores the foundations of lasting happiness from a biblical perspective.

--- Item 2 ---
Title:  Overcoming Anxiety and Fear
Author: Jane Doe
Relevance: 84%
Section:   Trusting God in Difficult Times
Summary:   Practical guidance on releasing anxiety through prayer and scripture.

--- Item 3 ---
Title:  Peace That Passes Understanding
Author: John Smith
Relevance: 81%
Section:   Philippians 4 Applied
Summary:   A study of Philippians 4:6-7 and its application to modern anxiety.
Fields like Summary and Section may not appear for every item — they are AI-generated metadata that depends on how content was processed during ingestion. Older or minimally processed content may return only Title, Author, and Relevance.

Run the Cookbook Example

The cookbook includes a ready-to-run basic search script for each language:
cd recommendations/python
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python recommend_base.py "How do I deal with anxiety?"
python recommend_base.py "parenting teenagers" 3
Getting no results? Work through these in order:
  1. Test your query in the Search Playground — if nothing shows there, your content may not be indexed yet.
  2. Try a different query that you know returns results in the Playground.
  3. If the Playground returns results but your code doesn’t, lower certainty_threshold from 0.75 to 0.5 to match the Playground’s default.

Step 2: Recommendations with Snippet Previews

The verbose endpoint adds full snippet text to each result. This is ideal for content preview cards where you want to show a passage alongside the title. The only change from Step 1 is the endpoint URL and reading uuids[0].snippet from the response.
import requests, os, sys, time
from dotenv import load_dotenv
load_dotenv()

CLIENT_ID     = os.getenv("GLOO_CLIENT_ID", "YOUR_CLIENT_ID")
CLIENT_SECRET = os.getenv("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET")
TENANT        = os.getenv("GLOO_TENANT", "your-tenant-name")
COLLECTION    = os.getenv("GLOO_COLLECTION", "GlooProd")
TOKEN_URL     = "https://platform.ai.gloo.com/oauth2/token"
VERBOSE_URL   = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/verbose"

token_info = {}

def ensure_token():
    if not token_info or time.time() > token_info.get("expires_at", 0) - 60:
        r = requests.post(TOKEN_URL, data={"grant_type": "client_credentials"},
                          auth=(CLIENT_ID, CLIENT_SECRET), timeout=30)
        r.raise_for_status()
        d = r.json()
        d["expires_at"] = int(time.time()) + d["expires_in"]
        token_info.update(d)
    return token_info["access_token"]

def get_verbose(query, item_count=5):
    token = ensure_token()
    r = requests.post(
        VERBOSE_URL,
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json={
            "query": query, "item_count": item_count, "certainty_threshold": 0.75,
            "collection": COLLECTION, "tenant": TENANT,
        },
        timeout=60,
    )
    r.raise_for_status()
    return r.json()

query      = sys.argv[1] if len(sys.argv) > 1 else "How do I deal with anxiety?"
item_count = int(sys.argv[2]) if len(sys.argv) > 2 else 5

items = get_verbose(query, item_count)
print(f"Found {len(items)} item(s):\n")
for i, item in enumerate(items, 1):
    print(f"--- Item {i} ---")
    print(f"Title:  {item.get('item_title', 'N/A')}")
    if item.get("author"):
        print(f"Author: {', '.join(item['author'])}")
    if item.get("uuids"):
        top = item["uuids"][0]
        print(f"Relevance: {top.get('certainty', 0):.0%}")
        if top.get("ai_title"):     print(f"Section:   {top['ai_title']}")
        if top.get("item_summary"): print(f"Summary:   {top['item_summary']}")
        snippet = top.get("snippet", "")
        if snippet:
            print(f"Preview:   \"{snippet[:200]}{'...' if len(snippet) > 200 else ''}\"")
    print()

What You’ll See

Same as Step 1, with a Preview line added for each item:
--- Item 1 ---
Title:  Finding True Happiness
Author: John Smith
Relevance: 89%
Section:   The Path to Contentment
Summary:   This article explores the foundations of lasting happiness from a biblical perspective.
Preview:   "True happiness is not found in circumstances but in relationship. The Beatitudes
            describe a person who is blessed not because of what they have, but because of who..."

Run the Cookbook Example

python recommend_verbose.py "How do I deal with anxiety?"
python recommend_verbose.py "parenting teenagers" 3

Step 3: Affiliate Network Discovery

The affiliate endpoint surfaces content from across the entire Gloo publisher network and not just your own collection. Use this to power “Explore More” sections that introduce users to resources from other publishers. Two key differences from the publisher endpoints:
  • No collection or tenant in the request — the search spans the full affiliate network
  • Flat response structure — items have tradition and item_subtitle fields instead of uuids
import requests, os, sys, time
from dotenv import load_dotenv
load_dotenv()

CLIENT_ID      = os.getenv("GLOO_CLIENT_ID", "YOUR_CLIENT_ID")
CLIENT_SECRET  = os.getenv("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET")
TOKEN_URL      = "https://platform.ai.gloo.com/oauth2/token"
AFFILIATES_URL = "https://platform.ai.gloo.com/ai/v1/data/affiliates/referenced-items"

token_info = {}

def ensure_token():
    if not token_info or time.time() > token_info.get("expires_at", 0) - 60:
        r = requests.post(TOKEN_URL, data={"grant_type": "client_credentials"},
                          auth=(CLIENT_ID, CLIENT_SECRET), timeout=30)
        r.raise_for_status()
        d = r.json()
        d["expires_at"] = int(time.time()) + d["expires_in"]
        token_info.update(d)
    return token_info["access_token"]

def get_affiliates(query, item_count=5):
    token = ensure_token()
    r = requests.post(
        AFFILIATES_URL,
        headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
        json={"query": query, "item_count": item_count, "certainty_threshold": 0.75},
        timeout=60,
    )
    r.raise_for_status()
    return r.json()

query      = sys.argv[1] if len(sys.argv) > 1 else "How do I deal with anxiety?"
item_count = int(sys.argv[2]) if len(sys.argv) > 2 else 5

items = get_affiliates(query, item_count)
print(f"Found {len(items)} item(s) from across the affiliate network:\n")
for i, item in enumerate(items, 1):
    print(f"--- Item {i} ---")
    print(f"Title:     {item.get('item_title', 'N/A')}")
    if item.get("author"):
        print(f"Author:    {', '.join(item['author'])}")
    if item.get("tradition"):
        print(f"Tradition: {item['tradition']}")
    if item.get("item_subtitle"):
        print(f"Subtitle:  {item['item_subtitle']}")
    if item.get("item_url"):
        print(f"URL:       {item['item_url']}")
    print()

What You’ll See

Found 3 item(s) from across the affiliate network:

--- Item 1 ---
Title:     Anxiety and the Christian Life
Author:    Crossway Publishers
Tradition: Evangelical
Subtitle:  Finding Peace in a Worried World
URL:       https://crossway.org/books/anxiety-and-the-christian-life

--- Item 2 ---
Title:     Cast All Your Anxiety on Him
Author:    Desiring God
Tradition: Reformed
URL:       https://www.desiringgod.org/articles/cast-all-your-anxiety-on-him

--- Item 3 ---
Title:     Freedom from Anxiety
Author:    Focus on the Family
Tradition: Evangelical
URL:       https://www.focusonthefamily.com/faith/freedom-from-anxiety

Run the Cookbook Example

You can try out the script in the cookbook to search the affiliate network.
python recommend_affiliates.py "How do I deal with anxiety?"
python recommend_affiliates.py "parenting teenagers" 3

Try It: Frontend Example

The cookbook includes a browser-based frontend that connects to a proxy server, letting you visualize all three recommendation modes. The proxy server keeps your credentials secure on the server side.

Architecture

Browser (HTML/JS) → Proxy Server (localhost:3000) → Gloo AI APIs
The proxy server exposes three endpoints:
  • POST /api/recommendations/base — Publisher recommendations (metadata only)
  • POST /api/recommendations/verbose — Publisher recommendations (with snippet previews)
  • POST /api/recommendations/affiliates — Affiliate network discovery

Start the Proxy Server

Each language includes a proxy server. Start one:
cd recommendations/python
source venv/bin/activate
python server.py
Then open http://localhost:3000 in your browser.

Publisher Recommendations

Enter a query, choose how many items to return, and optionally enable Verbose mode to show snippet previews alongside each result:
Publisher recommendation results showing content cards with relevance scores and optional snippet previews

Affiliate Network Discovery

The Explore More panel fires automatically alongside your publisher results, surfacing related content from across the Gloo network:
Affiliate recommendation results showing resources from across the Gloo publisher network
The frontend is language-agnostic — the same HTML/JS works with any language’s proxy server. This is one approach; customize for your branding and framework.

Complete Working Examples

View Complete Code

Clone or browse the complete working examples for all 6 languages (JavaScript, TypeScript, Python, PHP, Go, Java) with setup instructions, proxy servers, and a browser-based frontend.

What’s Included

Each language implementation provides:
  • auth — Shared OAuth2 token management with automatic refresh
  • config — Centralized configuration (URLs, env vars)
  • recommend_base — Publisher recommendations CLI (metadata only)
  • recommend_verbose — Publisher recommendations CLI (with snippet text)
  • recommend_affiliates — Affiliate network discovery CLI
  • server — Proxy server exposing REST endpoints for the frontend

Troubleshooting

No Results Returned

  • certainty_threshold too strict: The default 0.75 filters out lower-confidence matches. Lower it to 0.5 to broaden results.
  • Content not indexed: Test the same query in the Search Playground. If content appears there, your API parameters are correct.
  • Wrong tenant: Results are scoped to your publisher. Verify the tenant name matches your publisher in Organizations.

403 Forbidden

  • You can only access your own publisher’s content via the publisher endpoints.
  • Verify your Client ID and Client Secret are correct and not expired.
  • The affiliate endpoint has broader access — a 403 there indicates an authentication issue.

Affiliate Endpoint Returns Empty

  • Not all content is available in the affiliate network. Publishers must opt in.
  • Try a broader or different query to confirm the endpoint is working.

Authentication Errors

  • Tokens expire. Implement token refresh logic (see Authentication Tutorial).
  • Ensure you’re using Bearer {token} in the Authorization header.

Next Steps

Base Recommendations API Reference

Endpoint docs for recommendation results that return item metadata only.

Verbose Recommendations API Reference

Endpoint docs for recommendation results that include snippet previews.

Affiliate Referenced Items API Reference

Explore cross-publisher affiliate discovery for related content.

Upload Files

Add more content to your Data Engine for richer recommendations.