@embed-ai/sdk

The @embed-ai/sdk is a TypeScript/JavaScript SDK that makes it easy to integrate real-time, personalized Web3 feeds into your application. It provides a simple interface to the embed recommendation APIs with built-in error handling, automatic retries, and type safety.

Table of Contents


Installation

Install the SDK using your preferred package manager:

# Using bun (recommended)
bun install @embed-ai/sdk

# Using npm
npm install @embed-ai/sdk

# Using yarn
yarn add @embed-ai/sdk

# Using pnpm
pnpm add @embed-ai/sdk

Requirements

  • Node.js 18+ or Bun
  • TypeScript 4.5+ (optional, but recommended for type safety)

Quick Start

Get your first feed in three steps:

import { getClient } from "@embed-ai/sdk";

// 1. Initialize the client with your API key
const client = getClient(process.env.API_KEY_EMBED!);

// 2. Fetch a feed for a user
const feed = await client.feed.byUserId("16085", "feed_390", {
  top_k: 10,
});

// 3. Use the feed data
console.log(`Found ${feed.length} recommendations`);
feed.forEach((item) => {
  console.log(`Item: ${item.item_id}, Score: ${item.score}`);
});

Client Initialization

Basic Initialization

import { getClient } from "@embed-ai/sdk";

const client = getClient("mbd-your-api-key-here");

Using Environment Variables

import { getClient } from "@embed-ai/sdk";

const client = getClient(process.env.API_KEY_EMBED!);

Advanced Configuration

The SDK client includes built-in features:

  • Automatic Retries: Failed requests are automatically retried
  • Error Handling: Standardized error responses
  • Type Safety: Full TypeScript support
  • Request Formatting: Automatic header and body formatting

API Reference

Feed Methods

client.feed.byUserId(fid, feedId, options?)

Fetches personalized feed recommendations for a Farcaster user ID (FID).

Parameters:

  • fid (string, required): Farcaster ID of the user
  • feedId (string, required): Feed configuration ID (e.g., "feed_390")
  • options (object, optional): Request options
    • top_k (number, optional): Number of recommendations to return (1-500, default: 25)
    • return_metadata (boolean, optional): Include full post metadata (default: true)
    • impression_count (number, optional): Number of posts considered as seen by the user

Returns: Promise<FeedItem[]>

Example:

const feed = await client.feed.byUserId("16085", "feed_390", {
  top_k: 25,
  return_metadata: true,
  impression_count: 1,
});

client.feed.byWalletAddress(walletAddress, feedId, options?)

Fetches personalized feed recommendations for a wallet address.

Parameters:

  • walletAddress (string, required): Ethereum wallet address (must start with 0x)
  • feedId (string, required): Feed configuration ID
  • options (object, optional): Same as byUserId

Returns: Promise<FeedItem[]>

Example:

const feed = await client.feed.byWalletAddress(
  "0x1234567890123456789012345678901234567890",
  "feed_390",
  { top_k: 10 }
);

Response Types

FeedItem

Each item in the feed response contains the following structure:

interface FeedItem {
  // Identifiers
  item_id: string; // Post hash/ID (Ethereum address format)

  // Scoring Metrics
  popular_score: number; // Popularity score (0-1)
  trending_score: number; // Trending signal score (0-1)
  affinity_score: number; // User affinity score based on following graph
  score: number; // Final combined recommendation score
  adjusted_score?: number; // Score adjusted by time decay and other factors

  // Engagement Metrics
  text_length: number; // Length of post text in characters
  following_engagement?: {
    // Engagement from users the viewer follows
    comment: number;
    share: number;
    like: number;
  };

  // Social Proof
  social_proof?: Array<{
    // Users in viewer's network who interacted
    user_id: number; // FID of the user
    timestamp: number; // Unix timestamp of interaction
    interaction: "like" | "share" | "comment";
  }>;

  // Source Information
  source_feed: string; // Feed source identifier (e.g., "main:feed_3380")

  // Full Post Metadata (only included if return_metadata: true)
  metadata?: {
    // Content
    text: string; // Post content
    embed_items: string[]; // Array of embedded media URLs

    // Timestamps
    timestamp: number; // Unix timestamp when post was created

    // Threading
    root_parent_hash: string;
    parent_hash: string | null;
    root_parent_url: string | null;
    mentioned_profiles: string[];

    // AI Analysis
    ai_labels: {
      topics: string[]; // Topic categories
      sentiment: string[]; // Sentiment analysis
      emotion: string[]; // Emotional tone
      moderation: string[]; // Moderation flags
      web3_topics: string[]; // Web3-specific topics
    };

    // App Information
    app_fid: string; // FID of the app that created the post

    // Location
    geo_location: string | null; // Geographic location (lat,long)

    // Engagement Counts
    likes_count: number;
    comments_count: number;
    shares_count: number;

    // Author Information
    author: {
      user_id: number; // FID
      username: string;
      display_name: string;
      pfp_url: string; // Profile picture URL
      spam_score?: number; // Spam detection score
    };
  };
}

Example Response

[
  {
    item_id: "0x295c84a2e7973d8dd6cd6e946937da39c5138175",
    popular_score: 13.251785067452587,
    trending_score: 0.32665482296015197,
    affinity_score: 0.06666666666666667,
    following_engagement: {
      comment: 0,
      share: 0,
      like: 2,
    },
    social_proof: [
      {
        user_id: 8109,
        timestamp: 1752655366,
        interaction: "like",
      },
      {
        user_id: 646397,
        timestamp: 1752653278,
        interaction: "like",
      },
    ],
    score: 0.06666666666666667,
    source_feed: "warmup:main:feed_392",
    metadata: {
      text: "Eventually crypto will just be a part of everyday life...",
      embed_items: [],
      timestamp: 1752651678,
      root_parent_hash: "0x295c84a2e7973d8dd6cd6e946937da39c5138175",
      parent_hash: null,
      root_parent_url: null,
      mentioned_profiles: [],
      ai_labels: {
        topics: [],
        sentiment: [],
        emotion: [],
        moderation: [],
        web3_topics: [],
      },
      app_fid: "9152",
      geo_location: "45.81,9.09",
      likes_count: 21,
      comments_count: 11,
      shares_count: 2,
      author: {
        user_id: 2802,
        username: "garrett",
        display_name: "Garrett",
        pfp_url: "https://imagedelivery.net/BXluQx4ige9GuW0Ia56BHw/...",
      },
    },
  },
  // ... more items
];

Error Handling

The SDK automatically handles errors and retries failed requests. However, you should still handle errors in your application:

import { getClient } from "@embed-ai/sdk";

const client = getClient(process.env.API_KEY_EMBED!);

try {
  const feed = await client.feed.byUserId("16085", "feed_390", { top_k: 10 });
  // Use feed data
} catch (error) {
  if (error.status === 401) {
    console.error("Invalid API key");
  } else if (error.status === 400) {
    console.error("Invalid request:", error.message);
  } else if (error.status === 500) {
    console.error("Server error, please try again later");
  } else {
    console.error("Unexpected error:", error);
  }
}

Error Types

  • 401 Unauthorized: Invalid or missing API key
  • 400 Bad Request: Invalid parameters (e.g., invalid feed_id, user_id format)
  • 500 Internal Server Error: Server-side error

Configuration

Environment Variables

Store your API key securely:

# .env file
API_KEY_EMBED=mbd-your-api-key-here
// Load from environment
const client = getClient(process.env.API_KEY_EMBED!);

Retry Configuration

The SDK includes automatic retry logic for transient failures. Retry behavior is configured internally but may be customizable in advanced configurations.


Examples

Basic Feed Fetch

import { getClient } from "@embed-ai/sdk";

const client = getClient(process.env.API_KEY_EMBED!);

// Get feed for a user
const feed = await client.feed.byUserId("16085", "feed_390", {
  top_k: 25,
  return_metadata: true,
});

// Render feed items
feed.forEach((item, index) => {
  console.log(`${index + 1}. ${item.metadata?.text}`);
  console.log(`   Author: ${item.metadata?.author.display_name}`);
  console.log(`   Score: ${item.score}`);
  console.log(`   Likes: ${item.metadata?.likes_count}`);
});

Wallet-Based Feed

import { getClient } from "@embed-ai/sdk";

const client = getClient(process.env.API_KEY_EMBED!);

const walletAddress = "0x1234567890123456789012345678901234567890";

const feed = await client.feed.byWalletAddress(walletAddress, "feed_390", {
  top_k: 10,
  return_metadata: true,
});

Server-Side Rendering (Next.js API Route)

// app/api/feed/route.ts
import { getClient } from "@embed-ai/sdk";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const fid = searchParams.get("fid");
  const feedId = searchParams.get("feedId") || "feed_390";
  const topK = parseInt(searchParams.get("top_k") || "25");

  if (!fid) {
    return NextResponse.json(
      { error: "fid parameter is required" },
      { status: 400 }
    );
  }

  try {
    const client = getClient(process.env.API_KEY_EMBED!);
    const feed = await client.feed.byUserId(fid, feedId, {
      top_k: topK,
      return_metadata: true,
    });

    return NextResponse.json({ feed });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to fetch feed" },
      { status: 500 }
    );
  }
}

React Component Example

// components/Feed.tsx
"use client";

import { useEffect, useState } from "react";

interface FeedItem {
  item_id: string;
  score: number;
  metadata?: {
    text: string;
    author: {
      display_name: string;
      pfp_url: string;
    };
    likes_count: number;
  };
}

export function Feed({ fid }: { fid: string }) {
  const [feed, setFeed] = useState<FeedItem[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchFeed() {
      try {
        // Call your API route that uses the SDK
        const response = await fetch(`/api/feed?fid=${fid}`);
        const data = await response.json();
        setFeed(data.feed);
      } catch (error) {
        console.error("Failed to fetch feed:", error);
      } finally {
        setLoading(false);
      }
    }

    fetchFeed();
  }, [fid]);

  if (loading) return <div>Loading feed...</div>;

  return (
    <div>
      {feed.map((item) => (
        <div key={item.item_id} className="feed-item">
          <div className="author">
            <img src={item.metadata?.author.pfp_url} alt="Profile" />
            <span>{item.metadata?.author.display_name}</span>
          </div>
          <p>{item.metadata?.text}</p>
          <div className="engagement">
            <span>❤️ {item.metadata?.likes_count}</span>
            <span>Score: {item.score.toFixed(3)}</span>
          </div>
        </div>
      ))}
    </div>
  );
}

Filtering and Processing Feed Items

import { getClient } from "@embed-ai/sdk";

const client = getClient(process.env.API_KEY_EMBED!);

const feed = await client.feed.byUserId("16085", "feed_390", {
  top_k: 50,
  return_metadata: true,
});

// Filter high-scoring items
const highQualityItems = feed.filter((item) => item.score > 0.5);

// Filter by engagement
const popularItems = feed.filter(
  (item) => (item.metadata?.likes_count || 0) > 10
);

// Group by author
const byAuthor = feed.reduce((acc, item) => {
  const authorId = item.metadata?.author.user_id;
  if (authorId) {
    if (!acc[authorId]) acc[authorId] = [];
    acc[authorId].push(item);
  }
  return acc;
}, {} as Record<number, typeof feed>);

Available Feed Templates

The SDK works with pre-configured feed templates. Use these feedId values:

Feed IDNameDescription
feed_390For YouPersonalized feed based on user behavior
feed_624Zora FeedZora coin/posts feed
feed_625Short Form VideoVideo-focused content feed
feed_626MiniappsMini-app related content
feed_627New YorkLocation-based feed for New York
feed_628Coinbase Wallet CreatorsContent from Coinbase Wallet creators
feed_629Warpcast CreatorsContent from Warpcast creators

You can also use custom feed IDs created through the Feed Management API.


Best Practices

1. Server-Side Usage

Always use the SDK on the server side to keep your API keys secure:

// ✅ Good: Server-side (API route, server component)
// app/api/feed/route.ts
const client = getClient(process.env.API_KEY_EMBED!);
const feed = await client.feed.byUserId(fid, "feed_390");

// ❌ Bad: Client-side (exposes API key)
// components/Feed.tsx
const client = getClient("mbd-your-key"); // Don't do this!

2. Error Handling

Always wrap SDK calls in try-catch blocks:

try {
  const feed = await client.feed.byUserId(fid, feedId);
  // Process feed
} catch (error) {
  // Handle error appropriately
  console.error("Feed fetch failed:", error);
  // Return fallback or show error message
}

3. Caching

Consider caching feed responses to reduce API calls:

// Simple in-memory cache
const feedCache = new Map<string, { data: FeedItem[]; timestamp: number }>();
const CACHE_TTL = 60000; // 1 minute

async function getCachedFeed(fid: string, feedId: string) {
  const cacheKey = `${fid}-${feedId}`;
  const cached = feedCache.get(cacheKey);

  if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
    return cached.data;
  }

  const feed = await client.feed.byUserId(fid, feedId);
  feedCache.set(cacheKey, { data: feed, timestamp: Date.now() });
  return feed;
}

4. Type Safety

Use TypeScript for better type safety:

import { getClient } from "@embed-ai/sdk";

// TypeScript will provide autocomplete and type checking
const client = getClient(process.env.API_KEY_EMBED!);
const feed = await client.feed.byUserId("16085", "feed_390");
// feed is typed as FeedItem[]

5. Environment Variables

Never hardcode API keys:

// ✅ Good
const client = getClient(process.env.API_KEY_EMBED!);

// ❌ Bad
const client = getClient(
  "mbd-a4790a01cf57c80b08afc644e717c28c4e49f629bc399b55d75fb63a6bee885f"
);

6. Pagination

For large feeds, consider fetching in batches:

async function getPaginatedFeed(fid: string, feedId: string, pageSize = 25) {
  const allItems: FeedItem[] = [];
  let offset = 0;

  while (true) {
    const feed = await client.feed.byUserId(fid, feedId, {
      top_k: pageSize,
      // Note: The API may not support offset directly,
      // this is a conceptual example
    });

    if (feed.length === 0) break;
    allItems.push(...feed);
    offset += pageSize;

    // Add delay to avoid rate limiting
    await new Promise((resolve) => setTimeout(resolve, 100));
  }

  return allItems;
}

Troubleshooting

Common Issues

1. "Invalid API key" error

  • Verify your API key at console.mbd.xyz
  • Ensure the key starts with mbd-
  • Check that the key hasn't been revoked

2. "Invalid feed_id" error

  • Verify the feed ID exists and is accessible with your API key
  • Use one of the template feed IDs listed above
  • Check feed permissions in the console

3. Empty feed responses

  • The user may not have enough interaction history
  • Try a different feed template
  • Check that the user ID or wallet address is valid

4. Type errors in TypeScript

  • Ensure you're using TypeScript 4.5+
  • Check that @embed-ai/sdk types are properly installed
  • Restart your TypeScript server

Additional Resources


Support

For SDK issues or questions: