Skip to main content

Overview

FKAPI provides powerful search functionality for finding kits by keyword, club, season, and competition. FootyCollect wraps these capabilities through the FKAPIClient to enable seamless kit discovery.

Search Methods

Search Kits by Keyword

The primary search method for finding kits across the Football Kit Archive:
from footycollect.api.client import FKAPIClient

client = FKAPIClient()
results = client.search_kits("arsenal home 2023")
Implementation (client.py:423-434):
def search_kits(self, query: str) -> list[dict]:
    """Search kits by name."""
    logger.info("Searching kits with query: '%s'", query)
    result = self._get("/kits/search", params={"keyword": query})
    
    if result is None:
        logger.warning("FKAPI unavailable, returning empty results for kit search")
        return []
    
    results = self._extract_list_from_result(result)
    logger.info("Search returned %s results", len(results))
    return results
The search_kits() method automatically handles FKAPI unavailability by returning an empty list instead of raising an exception. Always check for empty results in your code.

Search Clubs

Search for clubs to find their kits by season:
clubs = client.search_clubs("manchester")
# Returns list of clubs matching "manchester"
# Example: [{"id": 123, "name": "Manchester United", "country": "England", ...}]
Implementation (client.py:400-403):
def search_clubs(self, query: str) -> list[dict]:
    """Search clubs by name."""
    result = self._get("/clubs/search", params={"keyword": query})
    return self._extract_list_from_result(result)

Search Brands

Find kits by manufacturer brand:
brands = client.search_brands("nike")
# Returns list of brands matching "nike"
Fallback Strategy (client.py:436-458): If the direct brand search endpoint is unavailable, the client falls back to extracting brands from kit search results:
if result is None:
    logger.warning("FKAPI unavailable, trying alternative method for brand search")
    kits = self.search_kits(query)
    brands = {}
    for kit in kits:
        brand = kit.get("brand") or kit.get("team", {}).get("brand")
        if brand:
            brand_name = brand.get("name") if isinstance(brand, dict) else brand
            if brand_name and brand_name not in brands:
                brands[brand_name] = {
                    "id": brand.get("id") if isinstance(brand, dict) else None,
                    "name": brand_name,
                }
    return list(brands.values())
The fallback strategy may return incomplete results compared to the direct API endpoint. It’s best used when FKAPI’s brand endpoint is temporarily unavailable.

Search Competitions

Find kits by competition (league, tournament):
competitions = client.search_competitions("champions league")
# Returns list of competitions matching "champions league"
Fallback Strategy (client.py:460-484): Similar to brand search, competition search has a fallback that extracts competitions from kit results.

Search Parameters

All search methods accept a keyword parameter:
keyword
string
required
The search query string. Supports partial matches and multiple words.Examples:
  • "arsenal" - Finds all Arsenal kits
  • "barcelona home" - Finds Barcelona home kits
  • "2023" - Finds kits from 2023 season

Minimum Query Length

Proxy endpoints enforce minimum query lengths to prevent excessive API calls:
# From views.py:66-79
@require_GET
def search_kits(request):
    query = request.GET.get("keyword", "")
    min_query_length = 3
    if len(query) < min_query_length:
        return JsonResponse({"results": []})
    
    client = FKAPIClient()
    results = client.search_kits(query)
    return JsonResponse({"results": results})
  • Kit search: Minimum 3 characters
  • Brand search: Minimum 2 characters
  • Competition search: Minimum 2 characters

Response Format

Kit Search Response

Kit search returns a list of kit objects with the following structure:
[
  {
    "id": 12345,
    "name": "Arsenal Home 2023-24",
    "slug": "arsenal-home-2023-24",
    "team": {
      "id": 1,
      "name": "Arsenal",
      "country": "England",
      "logo": "https://example.com/logos/arsenal.png"
    },
    "season": {
      "id": 100,
      "year": "2023-24"
    },
    "brand": {
      "id": 5,
      "name": "Adidas",
      "logo": "https://example.com/logos/adidas.png"
    },
    "type": "Home",
    "competition": [
      {
        "id": 10,
        "name": "Premier League"
      }
    ],
    "main_img_url": "https://example.com/kits/arsenal-home-2023.jpg",
    "kitcolor1": "RED",
    "kitcolor2": "WHITE",
    "design": "plain"
  }
]

Club Search Response

[
  {
    "id": 1,
    "name": "Arsenal",
    "slug": "arsenal",
    "country": "England",
    "logo": "https://example.com/logos/arsenal.png",
    "logo_dark": "https://example.com/logos/arsenal-dark.png"
  }
]

Using Search in FootyCollect

The search functionality is exposed through proxy endpoints for frontend use:
// Search kits as user types
fetch('/fkapi/kits/search/?keyword=arsenal')
  .then(response => response.json())
  .then(data => {
    console.log(data.results); // Array of kit objects
  });

Get Club Seasons

Once you have a club ID, fetch available seasons:
seasons = client.get_club_seasons(club_id=1)
# Returns: [{"id": 100, "year": "2023-24"}, {"id": 99, "year": "2022-23"}, ...]
Implementation (client.py:405-408):
def get_club_seasons(self, club_id: int) -> list[dict]:
    """Get seasons for a club."""
    result = self._get("/seasons", params={"id": club_id})
    return self._extract_list_from_result(result)

Get Club Kits by Season

Find all kits for a specific club and season:
kits = client.get_club_kits(club_id=1, season_id=100)
# Returns list of kits for Arsenal in 2023-24 season
Implementation (client.py:410-416):
def get_club_kits(self, club_id: int, season_id: int) -> list[dict]:
    """Get kits for a club for a specific season."""
    endpoint = f"/clubs/{club_id}/kits"
    params = {"season": season_id}
    result = self._get(endpoint, params=params)
    return self._extract_list_from_result(result)

Get Kit Details

Fetch complete details for a specific kit:
kit_details = client.get_kit_details(kit_id=12345)
# Returns full kit object with all metadata

# Force fresh data (bypass cache)
kit_details = client.get_kit_details(kit_id=12345, use_cache=False)
Implementation (client.py:418-421):
def get_kit_details(self, kit_id: int, *, use_cache: bool = True) -> dict | None:
    """Get complete details of a kit."""
    return self._get(f"/kits/{kit_id}", use_cache=use_cache)
The get_kit_details() method can return None if FKAPI is unavailable. Always check for None before using the result.

Complete Search Workflow

Here’s how to implement a complete kit search flow:
from footycollect.api.client import FKAPIClient

def find_and_display_kit(search_term: str):
    client = FKAPIClient()
    
    # Step 1: Search for kits
    kits = client.search_kits(search_term)
    
    if not kits:
        print("No kits found or FKAPI unavailable")
        return
    
    # Step 2: Display results
    for kit in kits:
        print(f"{kit['name']} - {kit['season']['year']}")
    
    # Step 3: Get detailed info for first kit
    first_kit_id = kits[0]['id']
    kit_details = client.get_kit_details(first_kit_id)
    
    if kit_details:
        print(f"\nDetailed info for {kit_details['name']}:")
        print(f"Brand: {kit_details['brand']['name']}")
        print(f"Type: {kit_details['type']}")
        print(f"Colors: {kit_details['kitcolor1']}, {kit_details['kitcolor2']}")

# Usage
find_and_display_kit("arsenal home 2023")

Advanced Search Patterns

Combine multiple search methods for refined results:
# 1. Find club
clubs = client.search_clubs("barcelona")
club_id = clubs[0]['id'] if clubs else None

if club_id:
    # 2. Get available seasons
    seasons = client.get_club_seasons(club_id)
    
    # 3. Get kits for latest season
    if seasons:
        latest_season = seasons[0]['id']
        kits = client.get_club_kits(club_id, latest_season)
        print(f"Found {len(kits)} kits for latest season")

Search with Caching Control

# Use cached results for fast browsing
kits = client.search_kits("liverpool")

# Force fresh results for critical operations
kit_details = client.get_kit_details(
    kit_id=12345,
    use_cache=False  # Bypass cache
)

Error Handling Best Practices

Always handle potential None returns and empty results:
def safe_kit_search(query: str) -> list[dict]:
    client = FKAPIClient()
    
    try:
        results = client.search_kits(query)
        
        # Handle None (FKAPI unavailable)
        if results is None:
            logger.warning(f"FKAPI unavailable for query: {query}")
            return []
        
        # Handle empty results
        if not results:
            logger.info(f"No kits found for query: {query}")
            return []
        
        return results
        
    except Exception as e:
        logger.exception(f"Error searching kits: {e}")
        return []

Rate Limiting Considerations

All search methods are subject to rate limiting:
  • Client-side: 100 requests per minute
  • Proxy endpoints: 100 requests per hour per IP
Use caching effectively to stay within rate limits. Most search queries are cached for 1 hour by default.

Next Steps