Skip to main content

Keyword Discovery

Discover creators by searching for specific keywords, hashtags, and topics across platforms.

Endpoint

POST /api/discovery/keywords

Request

Headers

Authorization
string
required
Bearer token for authentication
Content-Type
string
required
Must be application/json

Body Parameters

keywords
string[]
required
Array of keywords or hashtags to search for
platform
string
default:"tiktok"
Platform to search: tiktok, instagram, youtube, twitter
maxProfilesPerKeyword
number
default:"50"
Maximum profiles to return per keyword (1-100)
resultsPerPage
number
default:"100"
Results per page when scraping (affects performance)
filters
object
Additional filters

Response

success
boolean
Indicates if the request was successful
data
object

Examples

Search TikTok by Keywords

interface KeywordSearchRequest {
  keywords: string[];
  platform?: string;
  maxProfilesPerKeyword?: number;
  resultsPerPage?: number;
  filters?: {
    minFollowers?: number;
    minEngagement?: number;
    verified?: boolean;
    excludeUsernames?: string[];
  };
}

async function searchByKeywords(
  keywords: string[],
  options: Partial<KeywordSearchRequest> = {}
) {
  const response = await fetch(
    'https://your-domain.com/api/discovery/keywords',
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        keywords,
        platform: 'tiktok',
        maxProfilesPerKeyword: 50,
        ...options
      })
    }
  );

  return await response.json();
}

// Search for fitness influencers
const results = await searchByKeywords(
  ['fitness', 'workout', 'gym'],
  {
    filters: {
      minFollowers: 10000,
      minEngagement: 3.0,
      verified: true
    }
  }
);

console.log(`Discovered ${results.data.totalProfiles} fitness creators`);

Hashtag Discovery

// Search by hashtags (include # symbol)
const hashtagResults = await fetch(
  'https://your-domain.com/api/discovery/keywords',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      keywords: ['#tech', '#ai', '#webdev'],
      platform: 'tiktok',
      maxProfilesPerKeyword: 100
    })
  }
);

const { data } = await hashtagResults.json();

// Deduplicate and rank by engagement
const uniqueProfiles = Array.from(
  new Map(data.profiles.map(p => [p.username, p])).values()
);

const ranked = uniqueProfiles.sort((a, b) => {
  const engagementA = a.heartCount / a.followerCount;
  const engagementB = b.heartCount / b.followerCount;
  return engagementB - engagementA;
});

Exclude Already Tracked Creators

// Get existing creators from database
const existingCreators = await prisma.creatorProfile.findMany({
  where: {
    platformIdentifiers: {
      path: ['tiktok_username'],
      not: Prisma.JsonNull
    }
  },
  select: {
    platformIdentifiers: true
  }
});

const existingUsernames = existingCreators
  .map(c => c.platformIdentifiers.tiktok_username)
  .filter(Boolean);

// Search for new creators
const response = await fetch(
  'https://your-domain.com/api/discovery/keywords',
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      keywords: ['gaming'],
      platform: 'tiktok',
      filters: {
        excludeUsernames: existingUsernames
      }
    })
  }
);

Response Example

{
  "success": true,
  "data": {
    "profiles": [
      {
        "id": "7234567890",
        "username": "fitnesspro",
        "nickname": "Fitness Pro",
        "avatarUrl": "https://cdn.tiktok.com/avatar.jpg",
        "signature": "Fitness coach | Workout tips | Healthy lifestyle",
        "verified": true,
        "followerCount": 250000,
        "followingCount": 342,
        "videoCount": 456,
        "heartCount": 8900000,
        "profileUrl": "https://www.tiktok.com/@fitnesspro",
        "discoveryKeyword": "fitness"
      },
      {
        "id": "7234567891",
        "username": "workoutqueen",
        "nickname": "Workout Queen",
        "avatarUrl": "https://cdn.tiktok.com/avatar2.jpg",
        "signature": "Home workouts | Nutrition tips",
        "verified": false,
        "followerCount": 125000,
        "followingCount": 189,
        "videoCount": 289,
        "heartCount": 4500000,
        "profileUrl": "https://www.tiktok.com/@workoutqueen",
        "discoveryKeyword": "workout"
      }
    ],
    "totalProfiles": 2,
    "keywords": ["fitness", "workout", "gym"],
    "platform": "tiktok",
    "datasetId": "dataset_abc123",
    "runId": "run_xyz789"
  }
}

Implementation

The keyword discovery service uses the TikTok Apify scraper (defined in /lib/services/tiktok-apify-service.ts:300-375):
export class TikTokApifyService {
  async searchProfiles(
    keywords: string[],
    limit: number = 50
  ): Promise<TikTokUser[]> {
    // Run Apify actor with search queries
    const result = await this.actorManager.searchTikTokProfiles(keywords, {
      maxProfilesPerQuery: limit,
      resultsPerPage: 100,
    });
    
    // Get results from dataset
    const searchResults = await this.actorManager.getRunDataset(result.datasetId);
    
    // Transform and deduplicate
    const uniqueProfiles = Array.from(
      new Map(profiles.map(p => [p.uniqueId, p])).values()
    ).slice(0, limit);
    
    return uniqueProfiles;
  }
}

Cost Considerations

Keyword searches use Apify compute units:
  • Estimated cost: 0.100.10 - 0.50 per search (depending on maxProfilesPerKeyword)
  • Compute units: 10-50 CU per search
  • Duration: 2-10 minutes per search
Optimize costs by:
  1. Batch keywords together
  2. Cache results for 24 hours
  3. Use maxProfilesPerKeyword sparingly
  4. Set appropriate minFollowers filters

Error Responses

Invalid Platform

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid platform. Must be one of: tiktok, instagram, youtube, twitter"
  }
}

Rate Limit Exceeded

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Apify rate limit exceeded. Please try again in 10 minutes."
  }
}

Next Steps

Discovery Queue

Add discoveries to your queue

Creator Profile

Fetch detailed profiles

Build docs developers (and LLMs) love