Skip to content

Categories

The categories resource provides access to content categories on the Grapevine platform.

Overview

Categories help organize content and make it easier for users to discover relevant feeds. The categories resource provides:

  • List all available categories with pagination
  • Get a specific category by ID
  • Convenient methods for iteration and fetching all categories

Methods

MethodDescriptionAuth Required
list()List categories with paginationNo
get()Get category by IDNo
paginate()Async generator for paginationNo
getAll()Get all categories at onceNo

:::warning Deprecated Method The client.getCategories() method is deprecated. Use client.categories.list() or client.categories.getAll() instead. :::

Types

interface Category {
  id: string;               // UUID
  name: string;             // Display name
  description: string | null;
  icon_url: string | null;
  is_active: boolean;
  created_at: number;       // Unix timestamp
  updated_at: number;       // Unix timestamp
}
 
interface ListCategoriesQuery {
  page_size?: number;    // Results per page (default: 20)
  page_token?: string;   // Pagination token
  is_active?: boolean;   // Filter by active status
  search?: string;       // Search in name/description
}

list()

List categories with optional filtering and pagination.

Signature

categories.list(query?: ListCategoriesQuery): Promise<PaginatedResponse<Category>>

Usage

// Get first page of categories
const result = await grapevine.categories.list();
console.log('Categories:', result.data.map(c => c.name));
 
// With pagination
const page1 = await grapevine.categories.list({ page_size: 10 });
if (page1.has_more) {
  const page2 = await grapevine.categories.list({ 
    page_size: 10, 
    page_token: page1.next_page_token 
  });
}
 
// Filter active only
const active = await grapevine.categories.list({ is_active: true });
 
// Search categories
const techCategories = await grapevine.categories.list({ 
  search: 'technology' 
});

get()

Get a specific category by its ID.

Signature

categories.get(categoryId: string): Promise<Category>

Usage

const category = await grapevine.categories.get('123e4567-e89b-12d3-a456-426614174000');
 
console.log('Category:', category.name);
console.log('Description:', category.description);
console.log('Active:', category.is_active);

paginate()

Async generator that yields batches of categories, handling pagination automatically.

Signature

categories.paginate(
  query?: ListCategoriesQuery, 
  pageSize?: number
): AsyncGenerator<Category[]>

Usage

// Iterate through all categories
for await (const batch of grapevine.categories.paginate()) {
  console.log(`Processing ${batch.length} categories`);
  for (const category of batch) {
    console.log(`  - ${category.name}`);
  }
}
 
// With filters
for await (const batch of grapevine.categories.paginate({ is_active: true }, 50)) {
  // Process active categories
}

getAll()

Convenience method to fetch all categories at once.

Signature

categories.getAll(query?: Omit<ListCategoriesQuery, 'page_token' | 'page_size'>): Promise<Category[]>

Usage

// Get all categories
const allCategories = await grapevine.categories.getAll();
console.log(`Found ${allCategories.length} categories`);
 
// Get all active categories
const activeCategories = await grapevine.categories.getAll({ is_active: true });
 
// Search all categories
const techCategories = await grapevine.categories.getAll({ search: 'tech' });

Complete Examples

Category Selector Component

function CategorySelector({ 
  value, 
  onChange 
}: { 
  value?: string; 
  onChange: (id: string) => void;
}) {
  const [categories, setCategories] = useState<Category[]>([]);
  
  useEffect(() => {
    grapevine.categories.getAll({ is_active: true })
      .then(setCategories);
  }, []);
  
  return (
    <select value={value || ''} onChange={e => onChange(e.target.value)}>
      <option value="">Select a category</option>
      {categories.map(cat => (
        <option key={cat.id} value={cat.id}>
          {cat.name}
        </option>
      ))}
    </select>
  );
}

Category Browser

function CategoryBrowser() {
  const [categories, setCategories] = useState<Category[]>([]);
  const [selected, setSelected] = useState<Category | null>(null);
  
  useEffect(() => {
    grapevine.categories.getAll({ is_active: true })
      .then(setCategories);
  }, []);
  
  return (
    <div className="category-browser">
      <div className="category-list">
        {categories.map(cat => (
          <div 
            key={cat.id}
            className={`category-item ${selected?.id === cat.id ? 'selected' : ''}`}
            onClick={() => setSelected(cat)}
          >
            {cat.icon_url && <img src={cat.icon_url} alt="" />}
            <span>{cat.name}</span>
          </div>
        ))}
      </div>
      
      {selected && (
        <div className="category-details">
          <h2>{selected.name}</h2>
          {selected.description && <p>{selected.description}</p>}
          <button onClick={() => {
            // Navigate to feeds filtered by this category
            window.location.href = `/feeds?category=${selected.id}`;
          }}>
            Browse Feeds
          </button>
        </div>
      )}
    </div>
  );
}

Find Category by Name

async function findCategoryByName(name: string): Promise<Category | null> {
  const categories = await grapevine.categories.getAll();
  
  return categories.find(
    cat => cat.name.toLowerCase() === name.toLowerCase()
  ) || null;
}
 
// Usage
const techCategory = await findCategoryByName('Technology');
if (techCategory) {
  console.log(`Found: ${techCategory.name} (${techCategory.id})`);
}

Create Feed with Category

async function createCategorizedFeed(
  feedName: string, 
  categoryName: string
) {
  // Find the category
  const categories = await grapevine.categories.getAll();
  const category = categories.find(
    c => c.name.toLowerCase() === categoryName.toLowerCase()
  );
  
  if (!category) {
    throw new Error(`Category "${categoryName}" not found`);
  }
  
  // Create the feed
  const feed = await grapevine.feeds.create({
    name: feedName,
    category_id: category.id,
    tags: [categoryName.toLowerCase()]
  });
  
  return feed;
}
 
// Usage
const feed = await createCategorizedFeed('My Tech Blog', 'Technology');

Category Statistics

async function getCategoryUsage() {
  // Get all categories
  const categories = await grapevine.categories.getAll();
  
  // Get category stats from leaderboard
  const stats = await grapevine.leaderboards.categoryStats();
  
  // Combine data
  return categories.map(cat => {
    const catStats = stats.data.find(s => s.category_id === cat.id);
    
    return {
      id: cat.id,
      name: cat.name,
      description: cat.description,
      feeds: catStats ? parseInt(catStats.total_feeds) : 0,
      entries: catStats ? parseInt(catStats.total_entries) : 0,
      revenue: catStats ? catStats.total_revenue : '0'
    };
  });
}

Category Validation

When using categories with feed creation:

// ✅ Valid: No category (optional)
const feed1 = await grapevine.feeds.create({
  name: 'My Feed'
});
 
// ✅ Valid: With valid category ID
const categories = await grapevine.categories.getAll();
const feed2 = await grapevine.feeds.create({
  name: 'Categorized Feed',
  category_id: categories[0].id
});
 
// ❌ Invalid: Empty string
const feed3 = await grapevine.feeds.create({
  name: 'Bad Feed',
  category_id: ''  // ValidationError
});
 
// ❌ Invalid: Non-existent UUID
const feed4 = await grapevine.feeds.create({
  name: 'Bad Feed',
  category_id: '00000000-0000-0000-0000-000000000000'  // API error
});

Notes

  • Authentication: Not required - all category endpoints are public
  • Caching: Consider caching categories locally as they rarely change
  • Active Filter: Use is_active: true to only show enabled categories

Related