> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gloo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Custom Recommendations

> Learn how to surface relevant resources using the Gloo AI Recommendations API.

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.

<Info>
  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.
</Info>

## Prerequisites

Before starting, ensure you have:

* A Gloo AI Studio account
* Your Client ID and Client Secret from the [API Credentials page](/studio/manage-api-credentials)
* Your Tenant (publisher) name from [Organizations](/studio/manage-organizations) in Studio
* Content uploaded to the Data Engine (see [Upload Files Tutorial](/tutorials/upload-files))
* **Authentication setup** — Complete the [Authentication Tutorial](/tutorials/authentication) first

<Card title="Working Code Sample" icon="github" href="https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/recommendations">
  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.
</Card>

<Tip>
  The code snippets in this tutorial are **simplified and self-contained** —
  designed for readability and easy copy-paste. The [cookbook
  examples](https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/recommendations)
  use a modular architecture plus production niceties. Both implement the same
  APIs and patterns.
</Tip>

***

## Understanding the Recommendations API

Three endpoints cover the main recommendation use cases:

| Endpoint                                         | Purpose                                                    |
| ------------------------------------------------ | ---------------------------------------------------------- |
| `POST /ai/v1/data/items/recommendations/base`    | Publisher-scoped ranked items — metadata only              |
| `POST /ai/v1/data/items/recommendations/verbose` | Publisher-scoped ranked items — includes full snippet text |
| `POST /ai/v1/data/affiliates/referenced-items`   | Cross-publisher discovery from the Gloo affiliate network  |

### Publisher Endpoints (base & verbose)

Both publisher endpoints share the same required parameters:

| Parameter             | Description                                    |
| --------------------- | ---------------------------------------------- |
| `query`               | The topic or question to match content against |
| `item_count`          | Number of items to return (1–50)               |
| `certainty_threshold` | Minimum relevance score (0–1). Default: `0.75` |
| `collection`          | Always `"GlooProd"`                            |
| `tenant`              | Your publisher (tenant) name                   |

### Affiliate Endpoint

The affiliate endpoint searches across the full Gloo publisher network so no `collection` or `tenant` is required:

| Parameter             | Description                                    |
| --------------------- | ---------------------------------------------- |
| `query`               | The topic or question to match content against |
| `item_count`          | Number of items to return (1–50)               |
| `certainty_threshold` | Minimum 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:

```json theme={null}
[
  {
    "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.

<CodeGroup>
  ```python Python theme={null}
  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()
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');
  require('dotenv').config();

  const CLIENT_ID     = process.env.GLOO_CLIENT_ID     || 'YOUR_CLIENT_ID';
  const CLIENT_SECRET = process.env.GLOO_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
  const TENANT        = process.env.GLOO_TENANT        || 'your-tenant-name';
  const COLLECTION    = process.env.GLOO_COLLECTION    || 'GlooProd';
  const TOKEN_URL     = 'https://platform.ai.gloo.com/oauth2/token';
  const BASE_URL      = 'https://platform.ai.gloo.com/ai/v1/data/items/recommendations/base';

  let tokenInfo = {};

  async function ensureToken() {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at || 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token;
  }

  async function getRecommendations(query, itemCount = 5) {
    const token = await ensureToken();
    const { data } = await axios.post(
      BASE_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75, collection: COLLECTION, tenant: TENANT },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] || 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3]) || 5;

    const items = await getRecommendations(query, itemCount);
    console.log(`Found ${items.length} item(s):\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:  ${item.item_title || 'N/A'}`);
      if (item.author?.length) console.log(`Author: ${item.author.join(', ')}`);
      const top = item.uuids?.[0];
      if (top) {
        console.log(`Relevance: ${Math.round(top.certainty * 100)}%`);
        if (top.ai_title)     console.log(`Section:   ${top.ai_title}`);
        if (top.item_summary) console.log(`Summary:   ${top.item_summary}`);
      }
      console.log();
    });
  })();
  ```

  ```typescript TypeScript theme={null}
  import axios from 'axios';
  import * as dotenv from 'dotenv';
  dotenv.config();

  const CLIENT_ID     = process.env.GLOO_CLIENT_ID     ?? 'YOUR_CLIENT_ID';
  const CLIENT_SECRET = process.env.GLOO_CLIENT_SECRET ?? 'YOUR_CLIENT_SECRET';
  const TENANT        = process.env.GLOO_TENANT        ?? 'your-tenant-name';
  const COLLECTION    = process.env.GLOO_COLLECTION    ?? 'GlooProd';
  const TOKEN_URL     = 'https://platform.ai.gloo.com/oauth2/token';
  const BASE_URL      = 'https://platform.ai.gloo.com/ai/v1/data/items/recommendations/base';

  interface SnippetUUID {
    ai_title: string; item_summary: string; certainty: number;
  }
  interface RecommendationItem {
    item_title: string; author: string[]; item_url: string; uuids: SnippetUUID[];
  }

  let tokenInfo: { access_token?: string; expires_at?: number } = {};

  async function ensureToken(): Promise<string> {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at ?? 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token!;
  }

  async function getRecommendations(query: string, itemCount = 5): Promise<RecommendationItem[]> {
    const token = await ensureToken();
    const { data } = await axios.post<RecommendationItem[]>(
      BASE_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75, collection: COLLECTION, tenant: TENANT },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] ?? 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3] ?? '5');

    const items = await getRecommendations(query, itemCount);
    console.log(`Found ${items.length} item(s):\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:  ${item.item_title ?? 'N/A'}`);
      if (item.author?.length) console.log(`Author: ${item.author.join(', ')}`);
      const top = item.uuids?.[0];
      if (top) {
        console.log(`Relevance: ${Math.round(top.certainty * 100)}%`);
        if (top.ai_title)     console.log(`Section:   ${top.ai_title}`);
        if (top.item_summary) console.log(`Summary:   ${top.item_summary}`);
      }
      console.log();
    });
  })();
  ```

  ```php PHP theme={null}
  <?php
  declare(strict_types=1);
  require_once __DIR__ . '/vendor/autoload.php';
  $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad();

  $CLIENT_ID     = $_ENV['GLOO_CLIENT_ID']     ?? 'YOUR_CLIENT_ID';
  $CLIENT_SECRET = $_ENV['GLOO_CLIENT_SECRET'] ?? 'YOUR_CLIENT_SECRET';
  $TENANT        = $_ENV['GLOO_TENANT']        ?? 'your-tenant-name';
  $COLLECTION    = $_ENV['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';

  $tokenInfo = [];

  function ensureToken(): string {
      global $tokenInfo, $CLIENT_ID, $CLIENT_SECRET, $TOKEN_URL;
      if (empty($tokenInfo['expires_at']) || time() > $tokenInfo['expires_at'] - 60) {
          $ch = curl_init($TOKEN_URL);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POST           => true,
              CURLOPT_POSTFIELDS     => 'grant_type=client_credentials',
              CURLOPT_USERPWD        => "$CLIENT_ID:$CLIENT_SECRET",
              CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
          ]);
          $tokenInfo = json_decode(curl_exec($ch), true);
          curl_close($ch);
          $tokenInfo['expires_at'] = time() + $tokenInfo['expires_in'];
      }
      return $tokenInfo['access_token'];
  }

  function getRecommendations(string $query, int $itemCount = 5): array {
      global $COLLECTION, $TENANT, $BASE_URL;
      $token = ensureToken();
      $ch = curl_init($BASE_URL);
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_POST           => true,
          CURLOPT_POSTFIELDS     => json_encode([
              'query' => $query, 'item_count' => $itemCount,
              'certainty_threshold' => 0.75,
              'collection' => $COLLECTION, 'tenant' => $TENANT,
          ]),
          CURLOPT_HTTPHEADER => [
              'Authorization: Bearer ' . $token,
              'Content-Type: application/json',
          ],
          CURLOPT_TIMEOUT => 60,
      ]);
      $result = curl_exec($ch); curl_close($ch);
      return json_decode($result, true);
  }

  $query     = $argv[1] ?? 'How do I deal with anxiety?';
  $itemCount = (int)($argv[2] ?? 5);

  $items = getRecommendations($query, $itemCount);
  echo "Found " . count($items) . " item(s):\n\n";
  foreach ($items as $i => $item) {
      echo "--- Item " . ($i + 1) . " ---\n";
      echo "Title:  " . ($item['item_title'] ?? 'N/A') . "\n";
      if (!empty($item['author'])) echo "Author: " . implode(', ', $item['author']) . "\n";
      if (!empty($item['uuids'])) {
          $top = $item['uuids'][0];
          echo "Relevance: " . round($top['certainty'] * 100) . "%\n";
          if (!empty($top['ai_title']))     echo "Section:   " . $top['ai_title'] . "\n";
          if (!empty($top['item_summary'])) echo "Summary:   " . $top['item_summary'] . "\n";
      }
      echo "\n";
  }
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
      "net/url"
      "os"
      "strconv"
      "strings"
      "time"

      "github.com/joho/godotenv"
  )

  var (
      clientID, clientSecret, tenant, collection string
      tokenURL  = "https://platform.ai.gloo.com/oauth2/token"
      baseURL   = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/base"
      tokenData map[string]interface{}
  )

  func ensureToken() string {
      expiresAt, _ := tokenData["expires_at"].(float64)
      if tokenData == nil || float64(time.Now().Unix()) > expiresAt-60 {
          form := url.Values{"grant_type": {"client_credentials"}}
          req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(form.Encode()))
          req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
          req.SetBasicAuth(clientID, clientSecret)
          resp, _ := http.DefaultClient.Do(req)
          defer resp.Body.Close()
          body, _ := io.ReadAll(resp.Body)
          json.Unmarshal(body, &tokenData)
          exp, _ := tokenData["expires_in"].(float64)
          tokenData["expires_at"] = float64(time.Now().Unix()) + exp
      }
      return tokenData["access_token"].(string)
  }

  type RecommendationsRequest struct {
      Query              string  `json:"query"`
      ItemCount          int     `json:"item_count"`
      CertaintyThreshold float64 `json:"certainty_threshold"`
      Collection         string  `json:"collection"`
      Tenant             string  `json:"tenant"`
  }

  type SnippetUUID struct {
      AITitle     string  `json:"ai_title"`
      ItemSummary string  `json:"item_summary"`
      Certainty   float64 `json:"certainty"`
  }

  type RecommendationItem struct {
      ItemTitle string       `json:"item_title"`
      Author    []string     `json:"author"`
      ItemURL   string       `json:"item_url"`
      UUIDs     []SnippetUUID `json:"uuids"`
  }

  func getRecommendations(query string, itemCount int) []RecommendationItem {
      token := ensureToken()
      payload, _ := json.Marshal(RecommendationsRequest{
          Query: query, ItemCount: itemCount, CertaintyThreshold: 0.75,
          Collection: collection, Tenant: tenant,
      })
      req, _ := http.NewRequest("POST", baseURL, bytes.NewBuffer(payload))
      req.Header.Set("Authorization", "Bearer "+token)
      req.Header.Set("Content-Type", "application/json")
      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      body, _ := io.ReadAll(resp.Body)
      var items []RecommendationItem
      json.Unmarshal(body, &items)
      return items
  }

  func main() {
      godotenv.Load()
      clientID     = os.Getenv("GLOO_CLIENT_ID")
      clientSecret = os.Getenv("GLOO_CLIENT_SECRET")
      tenant       = os.Getenv("GLOO_TENANT")
      collection   = os.Getenv("GLOO_COLLECTION")
      if collection == "" { collection = "GlooProd" }

      query     := "How do I deal with anxiety?"
      itemCount := 5
      if len(os.Args) > 1 { query = os.Args[1] }
      if len(os.Args) > 2 { itemCount, _ = strconv.Atoi(os.Args[2]) }

      items := getRecommendations(query, itemCount)
      fmt.Printf("Found %d item(s):\n\n", len(items))
      for i, item := range items {
          fmt.Printf("--- Item %d ---\n", i+1)
          fmt.Printf("Title:  %s\n", item.ItemTitle)
          if len(item.Author) > 0 {
              fmt.Printf("Author: %s\n", strings.Join(item.Author, ", "))
          }
          if len(item.UUIDs) > 0 {
              top := item.UUIDs[0]
              fmt.Printf("Relevance: %.0f%%\n", top.Certainty*100)
              if top.AITitle != ""     { fmt.Printf("Section:   %s\n", top.AITitle) }
              if top.ItemSummary != "" { fmt.Printf("Summary:   %s\n", top.ItemSummary) }
          }
          fmt.Println()
      }
  }
  ```

  ```java Java theme={null}
  import com.google.gson.Gson;
  import com.google.gson.reflect.TypeToken;
  import io.github.cdimascio.dotenv.Dotenv;

  import java.net.URI;
  import java.net.http.*;
  import java.nio.charset.StandardCharsets;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.Base64;
  import java.util.List;

  public class Recommendations {

      static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
      static final String CLIENT_ID     = dotenv.get("GLOO_CLIENT_ID", "YOUR_CLIENT_ID");
      static final String CLIENT_SECRET = dotenv.get("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET");
      static final String TENANT        = dotenv.get("GLOO_TENANT", "your-tenant-name");
      static final String COLLECTION    = dotenv.get("GLOO_COLLECTION", "GlooProd");
      static final String TOKEN_URL     = "https://platform.ai.gloo.com/oauth2/token";
      static final String BASE_URL      = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/base";

      static final HttpClient http = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(30)).build();
      static final Gson gson = new Gson();
      static String accessToken; static long expiresAt;

      static String ensureToken() throws Exception {
          if (accessToken == null || Instant.now().getEpochSecond() > expiresAt - 60) {
              String auth = Base64.getEncoder().encodeToString(
                      (CLIENT_ID + ":" + CLIENT_SECRET).getBytes(StandardCharsets.UTF_8));
              HttpResponse<String> resp = http.send(
                  HttpRequest.newBuilder().uri(URI.create(TOKEN_URL))
                      .header("Content-Type", "application/x-www-form-urlencoded")
                      .header("Authorization", "Basic " + auth)
                      .POST(HttpRequest.BodyPublishers.ofString(
                              "grant_type=client_credentials&scope=api/access"))
                      .timeout(Duration.ofSeconds(30)).build(),
                  HttpResponse.BodyHandlers.ofString());
              var d = gson.fromJson(resp.body(), java.util.Map.class);
              accessToken = (String) d.get("access_token");
              expiresAt = Instant.now().getEpochSecond() + ((Double) d.get("expires_in")).longValue();
          }
          return accessToken;
      }

      static class Req { String query; int item_count; double certainty_threshold;
                         String collection; String tenant; }
      static class UUID { String ai_title; String item_summary; double certainty; }
      static class Item { String item_title; List<String> author; List<UUID> uuids; }

      static List<Item> getRecommendations(String query, int itemCount) throws Exception {
          Req payload = new Req();
          payload.query = query; payload.item_count = itemCount;
          payload.certainty_threshold = 0.75;
          payload.collection = COLLECTION; payload.tenant = TENANT;

          HttpResponse<String> resp = http.send(
              HttpRequest.newBuilder().uri(URI.create(BASE_URL))
                  .header("Authorization", "Bearer " + ensureToken())
                  .header("Content-Type", "application/json")
                  .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(payload)))
                  .timeout(Duration.ofSeconds(60)).build(),
              HttpResponse.BodyHandlers.ofString());
          return gson.fromJson(resp.body(), new TypeToken<List<Item>>() {}.getType());
      }

      public static void main(String[] args) throws Exception {
          String query  = args.length > 0 ? args[0] : "How do I deal with anxiety?";
          int itemCount = args.length > 1 ? Integer.parseInt(args[1]) : 5;

          List<Item> items = getRecommendations(query, itemCount);
          System.out.printf("Found %d item(s):%n%n", items.size());
          for (int i = 0; i < items.size(); i++) {
              Item item = items.get(i);
              System.out.printf("--- Item %d ---%n", i + 1);
              System.out.printf("Title:  %s%n", item.item_title != null ? item.item_title : "N/A");
              if (item.author != null && !item.author.isEmpty())
                  System.out.printf("Author: %s%n", String.join(", ", item.author));
              if (item.uuids != null && !item.uuids.isEmpty()) {
                  UUID top = item.uuids.get(0);
                  System.out.printf("Relevance: %.0f%%%n", top.certainty * 100);
                  if (top.ai_title != null)    System.out.printf("Section:   %s%n", top.ai_title);
                  if (top.item_summary != null) System.out.printf("Summary:   %s%n", top.item_summary);
              }
              System.out.println();
          }
      }
  }
  ```
</CodeGroup>

### 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:

<CodeGroup>
  ```bash Python theme={null}
  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
  ```

  ```bash JavaScript theme={null}
  cd recommendations/javascript
  npm install
  node recommend-base.js "How do I deal with anxiety?"
  node recommend-base.js "parenting teenagers" 3
  ```

  ```bash TypeScript theme={null}
  cd recommendations/typescript
  npm install
  npx ts-node recommend-base.ts "How do I deal with anxiety?"
  npx ts-node recommend-base.ts "parenting teenagers" 3
  ```

  ```bash PHP theme={null}
  cd recommendations/php
  composer install
  php recommend_base.php "How do I deal with anxiety?"
  php recommend_base.php "parenting teenagers" 3
  ```

  ```bash Go theme={null}
  cd recommendations/go
  go mod tidy
  go run . base "How do I deal with anxiety?"
  go run . base "parenting teenagers" 3
  ```

  ```bash Java theme={null}
  cd recommendations/java
  mvn compile -q
  mvn -q exec:java -Dexec.args='base "How do I deal with anxiety?"'
  mvn -q exec:java -Dexec.args='base "parenting teenagers" 3'
  ```
</CodeGroup>

<Tip>
  **Getting no results?** Work through these in order:

  1. Test your query in the [Search Playground](/studio/playground-search) — 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.
</Tip>

***

## 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.

<CodeGroup>
  ```python Python theme={null}
  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()
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');
  require('dotenv').config();

  const CLIENT_ID     = process.env.GLOO_CLIENT_ID     || 'YOUR_CLIENT_ID';
  const CLIENT_SECRET = process.env.GLOO_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
  const TENANT        = process.env.GLOO_TENANT        || 'your-tenant-name';
  const COLLECTION    = process.env.GLOO_COLLECTION    || 'GlooProd';
  const TOKEN_URL     = 'https://platform.ai.gloo.com/oauth2/token';
  const VERBOSE_URL   = 'https://platform.ai.gloo.com/ai/v1/data/items/recommendations/verbose';

  let tokenInfo = {};

  async function ensureToken() {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at || 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token;
  }

  async function getVerbose(query, itemCount = 5) {
    const token = await ensureToken();
    const { data } = await axios.post(
      VERBOSE_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75, collection: COLLECTION, tenant: TENANT },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] || 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3]) || 5;

    const items = await getVerbose(query, itemCount);
    console.log(`Found ${items.length} item(s):\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:  ${item.item_title || 'N/A'}`);
      if (item.author?.length) console.log(`Author: ${item.author.join(', ')}`);
      const top = item.uuids?.[0];
      if (top) {
        console.log(`Relevance: ${Math.round(top.certainty * 100)}%`);
        if (top.ai_title)     console.log(`Section:   ${top.ai_title}`);
        if (top.item_summary) console.log(`Summary:   ${top.item_summary}`);
        if (top.snippet) {
          const preview = top.snippet.length > 200 ? top.snippet.slice(0, 200) + '...' : top.snippet;
          console.log(`Preview:   "${preview}"`);
        }
      }
      console.log();
    });
  })();
  ```

  ```typescript TypeScript theme={null}
  import axios from 'axios';
  import * as dotenv from 'dotenv';
  dotenv.config();

  const CLIENT_ID     = process.env.GLOO_CLIENT_ID     ?? 'YOUR_CLIENT_ID';
  const CLIENT_SECRET = process.env.GLOO_CLIENT_SECRET ?? 'YOUR_CLIENT_SECRET';
  const TENANT        = process.env.GLOO_TENANT        ?? 'your-tenant-name';
  const COLLECTION    = process.env.GLOO_COLLECTION    ?? 'GlooProd';
  const TOKEN_URL     = 'https://platform.ai.gloo.com/oauth2/token';
  const VERBOSE_URL   = 'https://platform.ai.gloo.com/ai/v1/data/items/recommendations/verbose';

  interface SnippetUUID {
    ai_title: string; item_summary: string; certainty: number; snippet?: string;
  }
  interface RecommendationItem {
    item_title: string; author: string[]; uuids: SnippetUUID[];
  }

  let tokenInfo: { access_token?: string; expires_at?: number } = {};

  async function ensureToken(): Promise<string> {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at ?? 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token!;
  }

  async function getVerbose(query: string, itemCount = 5): Promise<RecommendationItem[]> {
    const token = await ensureToken();
    const { data } = await axios.post<RecommendationItem[]>(
      VERBOSE_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75, collection: COLLECTION, tenant: TENANT },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] ?? 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3] ?? '5');

    const items = await getVerbose(query, itemCount);
    console.log(`Found ${items.length} item(s):\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:  ${item.item_title ?? 'N/A'}`);
      if (item.author?.length) console.log(`Author: ${item.author.join(', ')}`);
      const top = item.uuids?.[0];
      if (top) {
        console.log(`Relevance: ${Math.round(top.certainty * 100)}%`);
        if (top.ai_title)     console.log(`Section:   ${top.ai_title}`);
        if (top.item_summary) console.log(`Summary:   ${top.item_summary}`);
        if (top.snippet) {
          const preview = top.snippet.length > 200 ? top.snippet.slice(0, 200) + '...' : top.snippet;
          console.log(`Preview:   "${preview}"`);
        }
      }
      console.log();
    });
  })();
  ```

  ```php PHP theme={null}
  <?php
  declare(strict_types=1);
  require_once __DIR__ . '/vendor/autoload.php';
  $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad();

  $CLIENT_ID     = $_ENV['GLOO_CLIENT_ID']     ?? 'YOUR_CLIENT_ID';
  $CLIENT_SECRET = $_ENV['GLOO_CLIENT_SECRET'] ?? 'YOUR_CLIENT_SECRET';
  $TENANT        = $_ENV['GLOO_TENANT']        ?? 'your-tenant-name';
  $COLLECTION    = $_ENV['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';

  $tokenInfo = [];

  function ensureToken(): string {
      global $tokenInfo, $CLIENT_ID, $CLIENT_SECRET, $TOKEN_URL;
      if (empty($tokenInfo['expires_at']) || time() > $tokenInfo['expires_at'] - 60) {
          $ch = curl_init($TOKEN_URL);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POST           => true,
              CURLOPT_POSTFIELDS     => 'grant_type=client_credentials',
              CURLOPT_USERPWD        => "$CLIENT_ID:$CLIENT_SECRET",
              CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
          ]);
          $tokenInfo = json_decode(curl_exec($ch), true);
          curl_close($ch);
          $tokenInfo['expires_at'] = time() + $tokenInfo['expires_in'];
      }
      return $tokenInfo['access_token'];
  }

  function getVerbose(string $query, int $itemCount = 5): array {
      global $COLLECTION, $TENANT, $VERBOSE_URL;
      $token = ensureToken();
      $ch = curl_init($VERBOSE_URL);
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_POST           => true,
          CURLOPT_POSTFIELDS     => json_encode([
              'query' => $query, 'item_count' => $itemCount,
              'certainty_threshold' => 0.75,
              'collection' => $COLLECTION, 'tenant' => $TENANT,
          ]),
          CURLOPT_HTTPHEADER => [
              'Authorization: Bearer ' . $token,
              'Content-Type: application/json',
          ],
          CURLOPT_TIMEOUT => 60,
      ]);
      $result = curl_exec($ch); curl_close($ch);
      return json_decode($result, true);
  }

  $query     = $argv[1] ?? 'How do I deal with anxiety?';
  $itemCount = (int)($argv[2] ?? 5);

  $items = getVerbose($query, $itemCount);
  echo "Found " . count($items) . " item(s):\n\n";
  foreach ($items as $i => $item) {
      echo "--- Item " . ($i + 1) . " ---\n";
      echo "Title:  " . ($item['item_title'] ?? 'N/A') . "\n";
      if (!empty($item['author'])) echo "Author: " . implode(', ', $item['author']) . "\n";
      if (!empty($item['uuids'])) {
          $top = $item['uuids'][0];
          echo "Relevance: " . round($top['certainty'] * 100) . "%\n";
          if (!empty($top['ai_title']))     echo "Section:   " . $top['ai_title'] . "\n";
          if (!empty($top['item_summary'])) echo "Summary:   " . $top['item_summary'] . "\n";
          if (!empty($top['snippet'])) {
              $preview = mb_substr($top['snippet'], 0, 200);
              if (mb_strlen($top['snippet']) > 200) $preview .= '...';
              echo "Preview:   \"$preview\"\n";
          }
      }
      echo "\n";
  }
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
      "net/url"
      "os"
      "strconv"
      "strings"
      "time"

      "github.com/joho/godotenv"
  )

  var (
      clientID, clientSecret, tenant, collection string
      tokenURL   = "https://platform.ai.gloo.com/oauth2/token"
      verboseURL = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/verbose"
      tokenData  map[string]interface{}
  )

  func ensureToken() string {
      expiresAt, _ := tokenData["expires_at"].(float64)
      if tokenData == nil || float64(time.Now().Unix()) > expiresAt-60 {
          form := url.Values{"grant_type": {"client_credentials"}}
          req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(form.Encode()))
          req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
          req.SetBasicAuth(clientID, clientSecret)
          resp, _ := http.DefaultClient.Do(req)
          defer resp.Body.Close()
          body, _ := io.ReadAll(resp.Body)
          json.Unmarshal(body, &tokenData)
          exp, _ := tokenData["expires_in"].(float64)
          tokenData["expires_at"] = float64(time.Now().Unix()) + exp
      }
      return tokenData["access_token"].(string)
  }

  type RecommendationsRequest struct {
      Query              string  `json:"query"`
      ItemCount          int     `json:"item_count"`
      CertaintyThreshold float64 `json:"certainty_threshold"`
      Collection         string  `json:"collection"`
      Tenant             string  `json:"tenant"`
  }

  type SnippetUUID struct {
      AITitle     string  `json:"ai_title"`
      ItemSummary string  `json:"item_summary"`
      Certainty   float64 `json:"certainty"`
      Snippet     string  `json:"snippet"`
  }

  type RecommendationItem struct {
      ItemTitle string        `json:"item_title"`
      Author    []string      `json:"author"`
      UUIDs     []SnippetUUID `json:"uuids"`
  }

  func getVerbose(query string, itemCount int) []RecommendationItem {
      token := ensureToken()
      payload, _ := json.Marshal(RecommendationsRequest{
          Query: query, ItemCount: itemCount, CertaintyThreshold: 0.75,
          Collection: collection, Tenant: tenant,
      })
      req, _ := http.NewRequest("POST", verboseURL, bytes.NewBuffer(payload))
      req.Header.Set("Authorization", "Bearer "+token)
      req.Header.Set("Content-Type", "application/json")
      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      body, _ := io.ReadAll(resp.Body)
      var items []RecommendationItem
      json.Unmarshal(body, &items)
      return items
  }

  func main() {
      godotenv.Load()
      clientID     = os.Getenv("GLOO_CLIENT_ID")
      clientSecret = os.Getenv("GLOO_CLIENT_SECRET")
      tenant       = os.Getenv("GLOO_TENANT")
      collection   = os.Getenv("GLOO_COLLECTION")
      if collection == "" { collection = "GlooProd" }

      query     := "How do I deal with anxiety?"
      itemCount := 5
      if len(os.Args) > 1 { query = os.Args[1] }
      if len(os.Args) > 2 { itemCount, _ = strconv.Atoi(os.Args[2]) }

      items := getVerbose(query, itemCount)
      fmt.Printf("Found %d item(s):\n\n", len(items))
      for i, item := range items {
          fmt.Printf("--- Item %d ---\n", i+1)
          fmt.Printf("Title:  %s\n", item.ItemTitle)
          if len(item.Author) > 0 {
              fmt.Printf("Author: %s\n", strings.Join(item.Author, ", "))
          }
          if len(item.UUIDs) > 0 {
              top := item.UUIDs[0]
              fmt.Printf("Relevance: %.0f%%\n", top.Certainty*100)
              if top.AITitle != ""     { fmt.Printf("Section:   %s\n", top.AITitle) }
              if top.ItemSummary != "" { fmt.Printf("Summary:   %s\n", top.ItemSummary) }
              if top.Snippet != "" {
                  preview := top.Snippet
                  if len(preview) > 200 { preview = preview[:200] + "..." }
                  fmt.Printf("Preview:   %q\n", preview)
              }
          }
          fmt.Println()
      }
  }
  ```

  ```java Java theme={null}
  import com.google.gson.Gson;
  import com.google.gson.reflect.TypeToken;
  import io.github.cdimascio.dotenv.Dotenv;

  import java.net.URI;
  import java.net.http.*;
  import java.nio.charset.StandardCharsets;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.Base64;
  import java.util.List;

  public class RecommendationsVerbose {

      static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
      static final String CLIENT_ID     = dotenv.get("GLOO_CLIENT_ID", "YOUR_CLIENT_ID");
      static final String CLIENT_SECRET = dotenv.get("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET");
      static final String TENANT        = dotenv.get("GLOO_TENANT", "your-tenant-name");
      static final String COLLECTION    = dotenv.get("GLOO_COLLECTION", "GlooProd");
      static final String TOKEN_URL     = "https://platform.ai.gloo.com/oauth2/token";
      static final String VERBOSE_URL   = "https://platform.ai.gloo.com/ai/v1/data/items/recommendations/verbose";

      static final HttpClient http = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(30)).build();
      static final Gson gson = new Gson();
      static String accessToken; static long expiresAt;

      static String ensureToken() throws Exception {
          if (accessToken == null || Instant.now().getEpochSecond() > expiresAt - 60) {
              String auth = Base64.getEncoder().encodeToString(
                      (CLIENT_ID + ":" + CLIENT_SECRET).getBytes(StandardCharsets.UTF_8));
              HttpResponse<String> resp = http.send(
                  HttpRequest.newBuilder().uri(URI.create(TOKEN_URL))
                      .header("Content-Type", "application/x-www-form-urlencoded")
                      .header("Authorization", "Basic " + auth)
                      .POST(HttpRequest.BodyPublishers.ofString(
                              "grant_type=client_credentials&scope=api/access"))
                      .timeout(Duration.ofSeconds(30)).build(),
                  HttpResponse.BodyHandlers.ofString());
              var d = gson.fromJson(resp.body(), java.util.Map.class);
              accessToken = (String) d.get("access_token");
              expiresAt = Instant.now().getEpochSecond() + ((Double) d.get("expires_in")).longValue();
          }
          return accessToken;
      }

      static class Req { String query; int item_count; double certainty_threshold;
                         String collection; String tenant; }
      static class UUID { String ai_title; String item_summary; double certainty; String snippet; }
      static class Item { String item_title; List<String> author; List<UUID> uuids; }

      static List<Item> getVerbose(String query, int itemCount) throws Exception {
          Req payload = new Req();
          payload.query = query; payload.item_count = itemCount;
          payload.certainty_threshold = 0.75;
          payload.collection = COLLECTION; payload.tenant = TENANT;

          HttpResponse<String> resp = http.send(
              HttpRequest.newBuilder().uri(URI.create(VERBOSE_URL))
                  .header("Authorization", "Bearer " + ensureToken())
                  .header("Content-Type", "application/json")
                  .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(payload)))
                  .timeout(Duration.ofSeconds(60)).build(),
              HttpResponse.BodyHandlers.ofString());
          return gson.fromJson(resp.body(), new TypeToken<List<Item>>() {}.getType());
      }

      public static void main(String[] args) throws Exception {
          String query  = args.length > 0 ? args[0] : "How do I deal with anxiety?";
          int itemCount = args.length > 1 ? Integer.parseInt(args[1]) : 5;

          List<Item> items = getVerbose(query, itemCount);
          System.out.printf("Found %d item(s):%n%n", items.size());
          for (int i = 0; i < items.size(); i++) {
              Item item = items.get(i);
              System.out.printf("--- Item %d ---%n", i + 1);
              System.out.printf("Title:  %s%n", item.item_title != null ? item.item_title : "N/A");
              if (item.author != null && !item.author.isEmpty())
                  System.out.printf("Author: %s%n", String.join(", ", item.author));
              if (item.uuids != null && !item.uuids.isEmpty()) {
                  UUID top = item.uuids.get(0);
                  System.out.printf("Relevance: %.0f%%%n", top.certainty * 100);
                  if (top.ai_title != null)    System.out.printf("Section:   %s%n", top.ai_title);
                  if (top.item_summary != null) System.out.printf("Summary:   %s%n", top.item_summary);
                  if (top.snippet != null && !top.snippet.isEmpty()) {
                      String preview = top.snippet.length() > 200
                              ? top.snippet.substring(0, 200) + "..." : top.snippet;
                      System.out.printf("Preview:   \"%s\"%n", preview);
                  }
              }
              System.out.println();
          }
      }
  }
  ```
</CodeGroup>

### 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

<CodeGroup>
  ```bash Python theme={null}
  python recommend_verbose.py "How do I deal with anxiety?"
  python recommend_verbose.py "parenting teenagers" 3
  ```

  ```bash JavaScript theme={null}
  node recommend-verbose.js "How do I deal with anxiety?"
  node recommend-verbose.js "parenting teenagers" 3
  ```

  ```bash TypeScript theme={null}
  npx ts-node recommend-verbose.ts "How do I deal with anxiety?"
  npx ts-node recommend-verbose.ts "parenting teenagers" 3
  ```

  ```bash PHP theme={null}
  php recommend_verbose.php "How do I deal with anxiety?"
  php recommend_verbose.php "parenting teenagers" 3
  ```

  ```bash Go theme={null}
  go run . verbose "How do I deal with anxiety?"
  go run . verbose "parenting teenagers" 3
  ```

  ```bash Java theme={null}
  mvn -q exec:java -Dexec.args='verbose "How do I deal with anxiety?"'
  mvn -q exec:java -Dexec.args='verbose "parenting teenagers" 3'
  ```
</CodeGroup>

***

## 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`

<CodeGroup>
  ```python Python theme={null}
  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()
  ```

  ```javascript JavaScript theme={null}
  const axios = require('axios');
  require('dotenv').config();

  const CLIENT_ID      = process.env.GLOO_CLIENT_ID     || 'YOUR_CLIENT_ID';
  const CLIENT_SECRET  = process.env.GLOO_CLIENT_SECRET || 'YOUR_CLIENT_SECRET';
  const TOKEN_URL      = 'https://platform.ai.gloo.com/oauth2/token';
  const AFFILIATES_URL = 'https://platform.ai.gloo.com/ai/v1/data/affiliates/referenced-items';

  let tokenInfo = {};

  async function ensureToken() {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at || 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token;
  }

  async function getAffiliates(query, itemCount = 5) {
    const token = await ensureToken();
    const { data } = await axios.post(
      AFFILIATES_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75 },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] || 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3]) || 5;

    const items = await getAffiliates(query, itemCount);
    console.log(`Found ${items.length} item(s) from across the affiliate network:\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:     ${item.item_title || 'N/A'}`);
      if (item.author?.length)  console.log(`Author:    ${item.author.join(', ')}`);
      if (item.tradition)       console.log(`Tradition: ${item.tradition}`);
      if (item.item_subtitle)   console.log(`Subtitle:  ${item.item_subtitle}`);
      if (item.item_url)        console.log(`URL:       ${item.item_url}`);
      console.log();
    });
  })();
  ```

  ```typescript TypeScript theme={null}
  import axios from 'axios';
  import * as dotenv from 'dotenv';
  dotenv.config();

  const CLIENT_ID      = process.env.GLOO_CLIENT_ID     ?? 'YOUR_CLIENT_ID';
  const CLIENT_SECRET  = process.env.GLOO_CLIENT_SECRET ?? 'YOUR_CLIENT_SECRET';
  const TOKEN_URL      = 'https://platform.ai.gloo.com/oauth2/token';
  const AFFILIATES_URL = 'https://platform.ai.gloo.com/ai/v1/data/affiliates/referenced-items';

  interface AffiliateItem {
    item_title: string; item_subtitle?: string; author: string[];
    tradition?: string; item_url?: string;
  }

  let tokenInfo: { access_token?: string; expires_at?: number } = {};

  async function ensureToken(): Promise<string> {
    if (!tokenInfo.access_token || Date.now() / 1000 > (tokenInfo.expires_at ?? 0) - 60) {
      const { data } = await axios.post(
        TOKEN_URL,
        new URLSearchParams({ grant_type: 'client_credentials' }).toString(),
        {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
          auth: { username: CLIENT_ID, password: CLIENT_SECRET },
        }
      );
      data.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
      tokenInfo = data;
    }
    return tokenInfo.access_token!;
  }

  async function getAffiliates(query: string, itemCount = 5): Promise<AffiliateItem[]> {
    const token = await ensureToken();
    const { data } = await axios.post<AffiliateItem[]>(
      AFFILIATES_URL,
      { query, item_count: itemCount, certainty_threshold: 0.75 },
      { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' } }
    );
    return data;
  }

  (async () => {
    const query     = process.argv[2] ?? 'How do I deal with anxiety?';
    const itemCount = parseInt(process.argv[3] ?? '5');

    const items = await getAffiliates(query, itemCount);
    console.log(`Found ${items.length} item(s) from across the affiliate network:\n`);
    items.forEach((item, i) => {
      console.log(`--- Item ${i + 1} ---`);
      console.log(`Title:     ${item.item_title ?? 'N/A'}`);
      if (item.author?.length)  console.log(`Author:    ${item.author.join(', ')}`);
      if (item.tradition)       console.log(`Tradition: ${item.tradition}`);
      if (item.item_subtitle)   console.log(`Subtitle:  ${item.item_subtitle}`);
      if (item.item_url)        console.log(`URL:       ${item.item_url}`);
      console.log();
    });
  })();
  ```

  ```php PHP theme={null}
  <?php
  declare(strict_types=1);
  require_once __DIR__ . '/vendor/autoload.php';
  $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); $dotenv->safeLoad();

  $CLIENT_ID     = $_ENV['GLOO_CLIENT_ID']     ?? 'YOUR_CLIENT_ID';
  $CLIENT_SECRET = $_ENV['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';

  $tokenInfo = [];

  function ensureToken(): string {
      global $tokenInfo, $CLIENT_ID, $CLIENT_SECRET, $TOKEN_URL;
      if (empty($tokenInfo['expires_at']) || time() > $tokenInfo['expires_at'] - 60) {
          $ch = curl_init($TOKEN_URL);
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POST           => true,
              CURLOPT_POSTFIELDS     => 'grant_type=client_credentials',
              CURLOPT_USERPWD        => "$CLIENT_ID:$CLIENT_SECRET",
              CURLOPT_HTTPHEADER     => ['Content-Type: application/x-www-form-urlencoded'],
          ]);
          $tokenInfo = json_decode(curl_exec($ch), true);
          curl_close($ch);
          $tokenInfo['expires_at'] = time() + $tokenInfo['expires_in'];
      }
      return $tokenInfo['access_token'];
  }

  function getAffiliates(string $query, int $itemCount = 5): array {
      global $AFFILIATES_URL;
      $token = ensureToken();
      $ch = curl_init($AFFILIATES_URL);
      curl_setopt_array($ch, [
          CURLOPT_RETURNTRANSFER => true,
          CURLOPT_POST           => true,
          CURLOPT_POSTFIELDS     => json_encode([
              'query' => $query, 'item_count' => $itemCount,
              'certainty_threshold' => 0.75,
          ]),
          CURLOPT_HTTPHEADER => [
              'Authorization: Bearer ' . $token,
              'Content-Type: application/json',
          ],
          CURLOPT_TIMEOUT => 60,
      ]);
      $result = curl_exec($ch); curl_close($ch);
      return json_decode($result, true);
  }

  $query     = $argv[1] ?? 'How do I deal with anxiety?';
  $itemCount = (int)($argv[2] ?? 5);

  $items = getAffiliates($query, $itemCount);
  echo "Found " . count($items) . " item(s) from across the affiliate network:\n\n";
  foreach ($items as $i => $item) {
      echo "--- Item " . ($i + 1) . " ---\n";
      echo "Title:     " . ($item['item_title'] ?? 'N/A') . "\n";
      if (!empty($item['author']))       echo "Author:    " . implode(', ', $item['author']) . "\n";
      if (!empty($item['tradition']))    echo "Tradition: " . $item['tradition'] . "\n";
      if (!empty($item['item_subtitle'])) echo "Subtitle:  " . $item['item_subtitle'] . "\n";
      if (!empty($item['item_url']))     echo "URL:       " . $item['item_url'] . "\n";
      echo "\n";
  }
  ```

  ```go Go theme={null}
  package main

  import (
      "bytes"
      "encoding/json"
      "fmt"
      "io"
      "net/http"
      "net/url"
      "os"
      "strconv"
      "strings"
      "time"

      "github.com/joho/godotenv"
  )

  var (
      clientID, clientSecret string
      tokenURL      = "https://platform.ai.gloo.com/oauth2/token"
      affiliatesURL = "https://platform.ai.gloo.com/ai/v1/data/affiliates/referenced-items"
      tokenData     map[string]interface{}
  )

  func ensureToken() string {
      expiresAt, _ := tokenData["expires_at"].(float64)
      if tokenData == nil || float64(time.Now().Unix()) > expiresAt-60 {
          form := url.Values{"grant_type": {"client_credentials"}}
          req, _ := http.NewRequest("POST", tokenURL, strings.NewReader(form.Encode()))
          req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
          req.SetBasicAuth(clientID, clientSecret)
          resp, _ := http.DefaultClient.Do(req)
          defer resp.Body.Close()
          body, _ := io.ReadAll(resp.Body)
          json.Unmarshal(body, &tokenData)
          exp, _ := tokenData["expires_in"].(float64)
          tokenData["expires_at"] = float64(time.Now().Unix()) + exp
      }
      return tokenData["access_token"].(string)
  }

  type AffiliatesRequest struct {
      Query              string  `json:"query"`
      ItemCount          int     `json:"item_count"`
      CertaintyThreshold float64 `json:"certainty_threshold"`
  }

  type AffiliateItem struct {
      ItemTitle    string   `json:"item_title"`
      ItemSubtitle string   `json:"item_subtitle"`
      Author       []string `json:"author"`
      Tradition    string   `json:"tradition"`
      ItemURL      string   `json:"item_url"`
  }

  func getAffiliates(query string, itemCount int) []AffiliateItem {
      token := ensureToken()
      payload, _ := json.Marshal(AffiliatesRequest{
          Query: query, ItemCount: itemCount, CertaintyThreshold: 0.75,
      })
      req, _ := http.NewRequest("POST", affiliatesURL, bytes.NewBuffer(payload))
      req.Header.Set("Authorization", "Bearer "+token)
      req.Header.Set("Content-Type", "application/json")
      resp, _ := http.DefaultClient.Do(req)
      defer resp.Body.Close()
      body, _ := io.ReadAll(resp.Body)
      var items []AffiliateItem
      json.Unmarshal(body, &items)
      return items
  }

  func main() {
      godotenv.Load()
      clientID     = os.Getenv("GLOO_CLIENT_ID")
      clientSecret = os.Getenv("GLOO_CLIENT_SECRET")

      query     := "How do I deal with anxiety?"
      itemCount := 5
      if len(os.Args) > 1 { query = os.Args[1] }
      if len(os.Args) > 2 { itemCount, _ = strconv.Atoi(os.Args[2]) }

      items := getAffiliates(query, itemCount)
      fmt.Printf("Found %d item(s) from across the affiliate network:\n\n", len(items))
      for i, item := range items {
          fmt.Printf("--- Item %d ---\n", i+1)
          fmt.Printf("Title:     %s\n", item.ItemTitle)
          if len(item.Author) > 0     { fmt.Printf("Author:    %s\n", strings.Join(item.Author, ", ")) }
          if item.Tradition != ""     { fmt.Printf("Tradition: %s\n", item.Tradition) }
          if item.ItemSubtitle != ""  { fmt.Printf("Subtitle:  %s\n", item.ItemSubtitle) }
          if item.ItemURL != ""       { fmt.Printf("URL:       %s\n", item.ItemURL) }
          fmt.Println()
      }
  }
  ```

  ```java Java theme={null}
  import com.google.gson.Gson;
  import com.google.gson.reflect.TypeToken;
  import io.github.cdimascio.dotenv.Dotenv;

  import java.net.URI;
  import java.net.http.*;
  import java.nio.charset.StandardCharsets;
  import java.time.Duration;
  import java.time.Instant;
  import java.util.Base64;
  import java.util.List;

  public class RecommendationsAffiliates {

      static final Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
      static final String CLIENT_ID      = dotenv.get("GLOO_CLIENT_ID", "YOUR_CLIENT_ID");
      static final String CLIENT_SECRET  = dotenv.get("GLOO_CLIENT_SECRET", "YOUR_CLIENT_SECRET");
      static final String TOKEN_URL      = "https://platform.ai.gloo.com/oauth2/token";
      static final String AFFILIATES_URL = "https://platform.ai.gloo.com/ai/v1/data/affiliates/referenced-items";

      static final HttpClient http = HttpClient.newBuilder()
              .connectTimeout(Duration.ofSeconds(30)).build();
      static final Gson gson = new Gson();
      static String accessToken; static long expiresAt;

      static String ensureToken() throws Exception {
          if (accessToken == null || Instant.now().getEpochSecond() > expiresAt - 60) {
              String auth = Base64.getEncoder().encodeToString(
                      (CLIENT_ID + ":" + CLIENT_SECRET).getBytes(StandardCharsets.UTF_8));
              HttpResponse<String> resp = http.send(
                  HttpRequest.newBuilder().uri(URI.create(TOKEN_URL))
                      .header("Content-Type", "application/x-www-form-urlencoded")
                      .header("Authorization", "Basic " + auth)
                      .POST(HttpRequest.BodyPublishers.ofString(
                              "grant_type=client_credentials&scope=api/access"))
                      .timeout(Duration.ofSeconds(30)).build(),
                  HttpResponse.BodyHandlers.ofString());
              var d = gson.fromJson(resp.body(), java.util.Map.class);
              accessToken = (String) d.get("access_token");
              expiresAt = Instant.now().getEpochSecond() + ((Double) d.get("expires_in")).longValue();
          }
          return accessToken;
      }

      static class Req { String query; int item_count; double certainty_threshold; }
      static class Item {
          String item_title; String item_subtitle; List<String> author;
          String tradition; String item_url;
      }

      static List<Item> getAffiliates(String query, int itemCount) throws Exception {
          Req payload = new Req();
          payload.query = query; payload.item_count = itemCount;
          payload.certainty_threshold = 0.75;

          HttpResponse<String> resp = http.send(
              HttpRequest.newBuilder().uri(URI.create(AFFILIATES_URL))
                  .header("Authorization", "Bearer " + ensureToken())
                  .header("Content-Type", "application/json")
                  .POST(HttpRequest.BodyPublishers.ofString(gson.toJson(payload)))
                  .timeout(Duration.ofSeconds(60)).build(),
              HttpResponse.BodyHandlers.ofString());
          return gson.fromJson(resp.body(), new TypeToken<List<Item>>() {}.getType());
      }

      public static void main(String[] args) throws Exception {
          String query  = args.length > 0 ? args[0] : "How do I deal with anxiety?";
          int itemCount = args.length > 1 ? Integer.parseInt(args[1]) : 5;

          List<Item> items = getAffiliates(query, itemCount);
          System.out.printf("Found %d item(s) from across the affiliate network:%n%n", items.size());
          for (int i = 0; i < items.size(); i++) {
              Item item = items.get(i);
              System.out.printf("--- Item %d ---%n", i + 1);
              System.out.printf("Title:     %s%n", item.item_title != null ? item.item_title : "N/A");
              if (item.author != null && !item.author.isEmpty())
                  System.out.printf("Author:    %s%n", String.join(", ", item.author));
              if (item.tradition != null && !item.tradition.isEmpty())
                  System.out.printf("Tradition: %s%n", item.tradition);
              if (item.item_subtitle != null && !item.item_subtitle.isEmpty())
                  System.out.printf("Subtitle:  %s%n", item.item_subtitle);
              if (item.item_url != null && !item.item_url.isEmpty())
                  System.out.printf("URL:       %s%n", item.item_url);
              System.out.println();
          }
      }
  }
  ```
</CodeGroup>

### 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.

<CodeGroup>
  ```bash Python theme={null}
  python recommend_affiliates.py "How do I deal with anxiety?"
  python recommend_affiliates.py "parenting teenagers" 3
  ```

  ```bash JavaScript theme={null}
  node recommend-affiliates.js "How do I deal with anxiety?"
  node recommend-affiliates.js "parenting teenagers" 3
  ```

  ```bash TypeScript theme={null}
  npx ts-node recommend-affiliates.ts "How do I deal with anxiety?"
  npx ts-node recommend-affiliates.ts "parenting teenagers" 3
  ```

  ```bash PHP theme={null}
  php recommend_affiliates.php "How do I deal with anxiety?"
  php recommend_affiliates.php "parenting teenagers" 3
  ```

  ```bash Go theme={null}
  go run . affiliates "How do I deal with anxiety?"
  go run . affiliates "parenting teenagers" 3
  ```

  ```bash Java theme={null}
  mvn -q exec:java -Dexec.args='affiliates "How do I deal with anxiety?"'
  mvn -q exec:java -Dexec.args='affiliates "parenting teenagers" 3'
  ```
</CodeGroup>

***

## 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:

<CodeGroup>
  ```bash Python theme={null}
  cd recommendations/python
  source venv/bin/activate
  python server.py
  ```

  ```bash JavaScript theme={null}
  cd recommendations/javascript
  node server.js
  ```

  ```bash TypeScript theme={null}
  cd recommendations/typescript
  npx ts-node server.ts
  ```

  ```bash PHP theme={null}
  cd recommendations/php
  php -S localhost:3000 server.php
  ```

  ```bash Go theme={null}
  cd recommendations/go
  go run . server
  ```

  ```bash Java theme={null}
  cd recommendations/java
  mvn -q exec:java -Dexec.args='server'
  ```
</CodeGroup>

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:

<Frame>
  <img src="https://mintcdn.com/gloo-b243725a/zvkaTgEU45vxgMLH/images/recommendations-tutorial_publisher_results.png?fit=max&auto=format&n=zvkaTgEU45vxgMLH&q=85&s=866134d70198f673686c02320396c216" alt="Publisher recommendation results showing content cards with relevance scores and optional snippet previews" width="1823" height="1195" data-path="images/recommendations-tutorial_publisher_results.png" />
</Frame>

### Affiliate Network Discovery

The **Explore More** panel fires automatically alongside your publisher results, surfacing related content from across the Gloo network:

<Frame>
  <img src="https://mintcdn.com/gloo-b243725a/zvkaTgEU45vxgMLH/images/recommendations-tutorial_affiliate_results.png?fit=max&auto=format&n=zvkaTgEU45vxgMLH&q=85&s=b4845817bef53b90f9a6e9c24e658e02" alt="Affiliate recommendation results showing resources from across the Gloo publisher network" width="1823" height="608" data-path="images/recommendations-tutorial_affiliate_results.png" />
</Frame>

<Info>
  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.
</Info>

***

## Complete Working Examples

<Card title="View Complete Code" icon="github" href="https://github.com/GlooDeveloper/gloo-ai-docs-cookbook/tree/main/recommendations">
  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.
</Card>

### 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](/studio/playground-search). 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](/studio/manage-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](/tutorials/authentication)).
* Ensure you're using `Bearer {token}` in the Authorization header.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Base Recommendations API Reference" href="/api-reference/data/get-base-item-recommendations" icon="book">
    Endpoint docs for recommendation results that return item metadata only.
  </Card>

  <Card title="Verbose Recommendations API Reference" href="/api-reference/data/get-verbose-item-recommendations" icon="book-open">
    Endpoint docs for recommendation results that include snippet previews.
  </Card>

  <Card title="Affiliate Referenced Items API Reference" href="/api-reference/data/get-affiliate-referenced-items" icon="globe">
    Explore cross-publisher affiliate discovery for related content.
  </Card>

  <Card title="Upload Files" href="/tutorials/upload-files" icon="upload">
    Add more content to your Data Engine for richer recommendations.
  </Card>
</CardGroup>
