FixFX

API References

Search

Search documentation and content across the FixFX platform.

The Search API provides powerful search capabilities across all FixFX documentation, guides, and content. Built on Fumadocs search infrastructure, it offers fast, relevant results with advanced filtering and ranking.

Overview

The Search API enables comprehensive search functionality across the FixFX platform:

  • Documentation Search - Find content across all documentation sections
  • Code Examples - Search through code snippets and examples
  • API References - Locate specific API endpoints and methods
  • Framework Guides - Search framework-specific documentation
  • Troubleshooting - Find solutions to common problems

The search system provides:

  • Real-time search suggestions
  • Relevance-based ranking
  • Category and section filtering
  • Highlighted search results
  • Fast response times with optimized indexing

Base URL

https://fixfx.wiki/api/search

Authentication

The Search API is public and doesn't require authentication. Results are based on publicly available documentation.

Endpoints

GET /api/search

Performs a search query across all indexed content.

Query Parameters

ParameterTypeDescriptionDefault
qstringSearch query stringRequired
limitnumberMaximum number of results (max 100)20
offsetnumberNumber of results to skip0
categorystringFilter by content categoryAll categories
sectionstringFilter by documentation sectionAll sections
typestringFilter by content type (page, heading, text)All types
highlightbooleanInclude search term highlightingtrue
suggestbooleanInclude search suggestionsfalse

Response Format

{
  "data": [
    {
      "id": "string",
      "title": "string",
      "content": "string",
      "url": "string",
      "category": "string",
      "section": "string",
      "type": "string",
      "score": number,
      "highlights": [
        {
          "field": "string",
          "matched": "string",
          "context": "string"
        }
      ],
      "metadata": {
        "lastModified": "string",
        "tags": ["string"],
        "difficulty": "string"
      }
    }
  ],
  "metadata": {
    "query": "string",
    "total": number,
    "took": number,
    "maxScore": number,
    "categories": {
      "docs": number,
      "guides": number,
      "api": number,
      "frameworks": number
    },
    "suggestions": ["string"],
    "filters": {
      "category": "string",
      "section": "string",
      "type": "string"
    },
    "pagination": {
      "limit": number,
      "offset": number,
      "currentPage": number,
      "totalPages": number
    }
  }
}

Example Usage

// Basic search query
const response = await fetch(
  'https://fixfx.wiki/api/search?q=player management&limit=10&highlight=true'
);
const data = await response.json();
 
// Display search results
console.log(`Found ${data.metadata.total} results in ${data.metadata.took}ms`);
 
data.data.forEach((result, index) => {
  console.log(`${index + 1}. ${result.title}`);
  console.log(`   URL: ${result.url}`);
  console.log(`   Category: ${result.category}`);
  console.log(`   Score: ${result.score}`);
  
  // Show highlights
  if (result.highlights && result.highlights.length > 0) {
    console.log('   Highlights:');
    result.highlights.forEach(highlight => {
      console.log(`     ${highlight.field}: "${highlight.matched}"`);
    });
  }
  
  // Show content excerpt
  const excerpt = result.content.length > 150 
    ? result.content.substring(0, 150) + '...'
    : result.content;
  console.log(`   ${excerpt}`);
  console.log('');
});
 
// Show suggestions if available
if (data.metadata.suggestions && data.metadata.suggestions.length > 0) {
  console.log('Suggestions:');
  data.metadata.suggestions.forEach(suggestion => {
    console.log(`- ${suggestion}`);
  });
}

Use Cases

Smart Search Interface

// Build an intelligent search interface
class SmartSearch {
  constructor(container) {
    this.container = container;
    this.debounceTimer = null;
    this.currentQuery = '';
    this.setupInterface();
  }
  
  setupInterface() {
    this.container.innerHTML = `
      <div class="search-box">
        <input type="text" placeholder="Search documentation..." id="search-input">
        <div class="search-suggestions" id="suggestions"></div>
        <div class="search-results" id="results"></div>
      </div>
    `;
    
    const input = this.container.querySelector('#search-input');
    input.addEventListener('input', (e) => this.handleInput(e.target.value));
  }
  
  handleInput(query) {
    clearTimeout(this.debounceTimer);
    this.currentQuery = query;
    
    if (query.length < 2) {
      this.clearResults();
      return;
    }
    
    this.debounceTimer = setTimeout(() => {
      this.performSearch(query);
    }, 300);
  }
  
  async performSearch(query) {
    try {
      // Get suggestions first
      const suggestResponse = await fetch(
        `https://fixfx.wiki/api/search?q=${encodeURIComponent(query)}&suggest=true&limit=5`
      );
      const suggestData = await suggestResponse.json();
      
      // Then get full results
      const response = await fetch(
        `https://fixfx.wiki/api/search?q=${encodeURIComponent(query)}&highlight=true&limit=20`
      );
      const data = await response.json();
      
      this.displaySuggestions(suggestData.metadata.suggestions || []);
      this.displayResults(data);
    } catch (error) {
      console.error('Search error:', error);
    }
  }
  
  displaySuggestions(suggestions) {
    const suggestionsEl = this.container.querySelector('#suggestions');
    
    if (suggestions.length === 0) {
      suggestionsEl.style.display = 'none';
      return;
    }
    
    suggestionsEl.innerHTML = suggestions
      .map(suggestion => `<div class="suggestion" onclick="this.parentNode.parentNode.querySelector('#search-input').value='${suggestion}'; this.parentNode.parentNode.dispatchEvent(new Event('search'))">${suggestion}</div>`)
      .join('');
    suggestionsEl.style.display = 'block';
  }
  
  displayResults(data) {
    const resultsEl = this.container.querySelector('#results');
    
    if (data.data.length === 0) {
      resultsEl.innerHTML = '<div class="no-results">No results found</div>';
      return;
    }
    
    const categoryFilter = this.buildCategoryFilter(data.metadata.categories);
    const resultsList = data.data.map(result => this.buildResultItem(result)).join('');
    
    resultsEl.innerHTML = `
      <div class="search-meta">
        Found ${data.metadata.total} results in ${data.metadata.took}ms
        ${categoryFilter}
      </div>
      <div class="results-list">${resultsList}</div>
    `;
  }
  
  buildCategoryFilter(categories) {
    const filters = Object.entries(categories)
      .filter(([, count]) => count > 0)
      .map(([category, count]) => `<span class="category-filter" data-category="${category}">${category} (${count})</span>`)
      .join('');
    
    return `<div class="category-filters">${filters}</div>`;
  }
  
  buildResultItem(result) {
    const highlights = result.highlights
      ? result.highlights.map(h => `<span class="highlight">${h.matched}</span>`).join(', ')
      : '';
    
    return `
      <div class="result-item" data-category="${result.category}">
        <h3><a href="${result.url}">${result.title}</a></h3>
        <div class="result-meta">
          <span class="category">${result.category}</span>
          <span class="score">Score: ${result.score.toFixed(2)}</span>
        </div>
        <p class="result-content">${result.content.substring(0, 200)}...</p>
        ${highlights ? `<div class="highlights">Matches: ${highlights}</div>` : ''}
      </div>
    `;
  }
  
  clearResults() {
    this.container.querySelector('#suggestions').style.display = 'none';
    this.container.querySelector('#results').innerHTML = '';
  }
}
 
// Initialize search
const searchContainer = document.getElementById('search-container');
const smartSearch = new SmartSearch(searchContainer);

Content Discovery

// Discover related content
async function discoverRelatedContent(currentPage) {
  // Extract key terms from current page
  const keyTerms = extractKeyTerms(currentPage);
  
  const relatedContent = [];
  
  for (const term of keyTerms.slice(0, 3)) {
    const response = await fetch(
      `https://fixfx.wiki/api/search?q=${encodeURIComponent(term)}&limit=5`
    );
    const data = await response.json();
    
    // Filter out current page and add to related content
    const related = data.data
      .filter(item => item.url !== currentPage.url)
      .slice(0, 2);
    
    relatedContent.push(...related);
  }
  
  // Remove duplicates and sort by relevance
  const uniqueContent = relatedContent
    .filter((item, index, self) => 
      index === self.findIndex(t => t.url === item.url)
    )
    .sort((a, b) => b.score - a.score)
    .slice(0, 6);
  
  return uniqueContent;
}
 
function extractKeyTerms(page) {
  // Simple keyword extraction from title and content
  const text = `${page.title} ${page.content}`.toLowerCase();
  const words = text.match(/\b\w{4,}\b/g) || [];
  
  // Count word frequency
  const frequency = {};
  words.forEach(word => {
    frequency[word] = (frequency[word] || 0) + 1;
  });
  
  // Return top terms
  return Object.entries(frequency)
    .sort(([,a], [,b]) => b - a)
    .slice(0, 10)
    .map(([word]) => word);
}
 
// Example usage
const currentPage = {
  title: "ESX Player Management",
  content: "Learn how to manage players in ESX framework...",
  url: "/docs/frameworks/esx/player-management"
};
 
discoverRelatedContent(currentPage).then(related => {
  console.log('Related Content:', related);
});

Search Analytics

// Track search analytics
class SearchAnalytics {
  constructor() {
    this.searchHistory = [];
    this.popularQueries = new Map();
    this.categoryPreferences = new Map();
  }
  
  trackSearch(query, results, category = null) {
    const searchEvent = {
      query,
      timestamp: new Date().toISOString(),
      resultCount: results.metadata.total,
      searchTime: results.metadata.took,
      category,
      topResult: results.data[0]?.url || null
    };
    
    this.searchHistory.push(searchEvent);
    
    // Update popular queries
    const count = this.popularQueries.get(query) || 0;
    this.popularQueries.set(query, count + 1);
    
    // Update category preferences
    if (category) {
      const catCount = this.categoryPreferences.get(category) || 0;
      this.categoryPreferences.set(category, catCount + 1);
    }
    
    // Limit history size
    if (this.searchHistory.length > 1000) {
      this.searchHistory = this.searchHistory.slice(-500);
    }
  }
  
  getPopularQueries(limit = 10) {
    return Array.from(this.popularQueries.entries())
      .sort(([,a], [,b]) => b - a)
      .slice(0, limit)
      .map(([query, count]) => ({ query, count }));
  }
  
  getCategoryPreferences() {
    const total = Array.from(this.categoryPreferences.values())
      .reduce((sum, count) => sum + count, 0);
    
    return Array.from(this.categoryPreferences.entries())
      .map(([category, count]) => ({
        category,
        count,
        percentage: (count / total * 100).toFixed(1)
      }))
      .sort((a, b) => b.count - a.count);
  }
  
  getSearchTrends(days = 7) {
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - days);
    
    const recentSearches = this.searchHistory.filter(
      search => new Date(search.timestamp) > cutoff
    );
    
    // Group by day
    const trends = {};
    recentSearches.forEach(search => {
      const day = search.timestamp.split('T')[0];
      trends[day] = (trends[day] || 0) + 1;
    });
    
    return Object.entries(trends)
      .sort(([a], [b]) => a.localeCompare(b))
      .map(([date, count]) => ({ date, count }));
  }
  
  generateReport() {
    const popularQueries = this.getPopularQueries();
    const categoryPrefs = this.getCategoryPreferences();
    const trends = this.getSearchTrends();
    
    console.log('Search Analytics Report');
    console.log('======================');
    console.log(`Total Searches: ${this.searchHistory.length}`);
    console.log('');
    
    console.log('Popular Queries:');
    popularQueries.forEach((item, index) => {
      console.log(`${index + 1}. "${item.query}" (${item.count} searches)`);
    });
    console.log('');
    
    console.log('Category Preferences:');
    categoryPrefs.forEach(item => {
      console.log(`- ${item.category}: ${item.count} searches (${item.percentage}%)`);
    });
    console.log('');
    
    console.log('Search Trends (Last 7 Days):');
    trends.forEach(item => {
      console.log(`${item.date}: ${item.count} searches`);
    });
    
    return {
      totalSearches: this.searchHistory.length,
      popularQueries,
      categoryPreferences: categoryPrefs,
      trends
    };
  }
}
 
// Usage example
const analytics = new SearchAnalytics();
 
// Track searches (would be called during actual searches)
async function performTrackedSearch(query, category = null) {
  const response = await fetch(
    `https://fixfx.wiki/api/search?q=${encodeURIComponent(query)}&category=${category || ''}`
  );
  const results = await response.json();
  
  analytics.trackSearch(query, results, category);
  return results;
}
 
// Example tracked searches
await performTrackedSearch('ESX player management', 'frameworks');
await performTrackedSearch('vehicle spawning', 'guides');
await performTrackedSearch('database setup');
 
// Generate report
analytics.generateReport();

Search Optimization

Index Coverage

The search index includes:

  • All documentation pages and sections
  • Code examples and snippets
  • API endpoint descriptions
  • Framework-specific guides
  • Troubleshooting content
  • Best practices and tips

Ranking Factors

Search results are ranked based on:

  1. Exact matches in titles and headings
  2. Term frequency in content
  3. Content type (pages ranked higher than fragments)
  4. Recency of last update
  5. User engagement metrics
  6. Category relevance to query context

Performance Features

  • Real-time indexing for immediate content availability
  • Fuzzy matching for handling typos and variations
  • Stemming for finding related word forms
  • Stop word filtering for better relevance
  • Result caching for common queries

Rate Limiting

  • 300 requests per minute per IP address
  • Burst allowance of 50 requests
  • Search suggestions have separate, higher limits

Error Handling

Standard HTTP status codes:

  • 200: Success
  • 400: Bad Request - Invalid query parameters
  • 429: Too Many Requests - Rate limit exceeded
  • 500: Server Error - Search service error

Example error response:

{
  "error": "Invalid query",
  "message": "Search query must be at least 2 characters long",
  "statusCode": 400
}

Best Practices

  1. Query Optimization

    • Use specific terms for better results
    • Combine category filters with queries
    • Implement search suggestions for user guidance
  2. User Experience

    • Debounce search input to reduce API calls
    • Show search progress indicators
    • Provide clear "no results" messaging
  3. Performance

    • Cache frequent search results
    • Implement proper pagination
    • Use appropriate result limits
  4. Analytics

    • Track popular search terms
    • Monitor search success rates
    • Identify content gaps from unsuccessful searches

Support

For questions about the Search API, please join our Discord. We can help with:

  • Search integration
  • Query optimization
  • Result ranking issues
  • Custom search implementations

The Search API is continuously improved based on user search patterns and feedback.

Use category filtering to help users find content faster in specific documentation sections.

On this page