Skip to content

Latest commit

 

History

History
477 lines (353 loc) · 9.33 KB

File metadata and controls

477 lines (353 loc) · 9.33 KB

PodcastIndex API Integration Guide

🔐 Authentication

The PodcastIndex API uses SHA1 hash-based authentication with three headers:

X-Auth-Key: YOUR_API_KEY
X-Auth-Date: UNIX_TIMESTAMP
Authorization: SHA1_HASH

How It Works

  1. Get current Unix timestamp
  2. Concatenate: apiKey + apiSecret + timestamp
  3. Generate SHA1 hash of the concatenated string
  4. Send all three values in headers

Implementation

// In NetworkService.swift
let authTime = String(Int(Date().timeIntervalSince1970))
let authHash = generateAuthHash(authTime: authTime)

request.setValue(APIConstants.apiKey, forHTTPHeaderField: "X-Auth-Key")
request.setValue(authTime, forHTTPHeaderField: "X-Auth-Date")
request.setValue(authHash, forHTTPHeaderField: "Authorization")

📡 API Endpoints Used

1. Search Podcasts

Endpoint: /search/byterm

Purpose: Search for podcasts by keyword

Parameters:

  • q (required): Search term

Example Request:

GET https://api.podcastindex.org/api/1.0/search/byterm?q=tech

Response:

{
  "status": "true",
  "feeds": [
    {
      "id": 920666,
      "title": "Tech News Today",
      "author": "TWiT",
      "description": "The most important tech news...",
      "artwork": "https://example.com/artwork.jpg",
      "url": "https://example.com/feed.xml",
      "categories": {
        "1": "Technology",
        "2": "News"
      }
    }
  ]
}

Used In: SearchPodcastsUseCase


2. Get Trending Podcasts

Endpoint: /podcasts/trending

Purpose: Get currently trending podcasts

Parameters:

  • max (optional): Maximum number of results (default: 10)

Example Request:

GET https://api.podcastindex.org/api/1.0/podcasts/trending?max=20

Response: Same structure as search

Used In: GetTrendingPodcastsUseCase


3. Get Podcast by Feed ID

Endpoint: /podcasts/byfeedid

Purpose: Get detailed information about a specific podcast

Parameters:

  • id (required): Feed ID

Example Request:

GET https://api.podcastindex.org/api/1.0/podcasts/byfeedid?id=920666

Response: Same structure as search (single feed)

Used In: PodcastRepository.getPodcastById()


4. Get Episodes by Feed ID

Endpoint: /episodes/byfeedid

Purpose: Get all episodes for a specific podcast

Parameters:

  • id (required): Feed ID
  • max (optional): Maximum number of results (default: 10)

Example Request:

GET https://api.podcastindex.org/api/1.0/episodes/byfeedid?id=920666&max=50

Response:

{
  "status": "true",
  "items": [
    {
      "id": 123456789,
      "title": "Episode 245: AI Revolution",
      "description": "We discuss the latest in AI...",
      "enclosureUrl": "https://example.com/episode.mp3",
      "duration": 2730,
      "datePublished": 1716163200,
      "image": "https://example.com/episode-art.jpg"
    }
  ]
}

Used In: GetEpisodesUseCase


🔄 Data Transformation

DTO to Entity Mapping

PodcastDTO → Podcast

struct PodcastDTO: Codable {
    let id: Int
    let title: String
    let author: String?
    let description: String?
    let artwork: String?
    let url: String
    let categories: [String: String]?
    
    func toDomain() -> Podcast {
        Podcast(
            id: id,
            title: title,
            author: author ?? "Unknown",
            description: description,
            artworkURL: artwork,
            feedURL: url,
            categories: categories?.values.map { $0 } ?? []
        )
    }
}

EpisodeDTO → Episode

struct EpisodeDTO: Codable {
    let id: Int
    let title: String
    let description: String?
    let enclosureUrl: String
    let duration: Int
    let datePublished: Int
    let image: String?
    
    func toDomain() -> Episode {
        Episode(
            id: id,
            title: title,
            description: description,
            audioURL: enclosureUrl,
            duration: duration,
            publishDate: Date(timeIntervalSince1970: TimeInterval(datePublished)),
            artworkURL: image
        )
    }
}

🛠️ Network Service Architecture

Request Flow

ViewModel
    ↓
UseCase
    ↓
Repository
    ↓
NetworkService
    ↓
URLSession
    ↓
PodcastIndex API
    ↓
JSON Response
    ↓
DTO (Decodable)
    ↓
Entity (Domain Model)
    ↓
ViewModel
    ↓
View

Error Handling

enum NetworkError: Error, LocalizedError {
    case invalidURL
    case invalidResponse
    case decodingError
    case serverError(String)
    
    var errorDescription: String? {
        switch self {
        case .invalidURL:
            return "Invalid URL"
        case .invalidResponse:
            return "Invalid response from server"
        case .decodingError:
            return "Failed to decode response"
        case .serverError(let message):
            return message
        }
    }
}

📊 API Response Examples

Successful Response

{
  "status": "true",
  "feeds": [...],
  "count": 20,
  "query": "tech",
  "description": "Found matching feeds."
}

Error Response

{
  "status": "false",
  "error": "Invalid API key",
  "description": "The API key provided is not valid."
}

⚡ Performance Optimization

1. Debounced Search

$searchText
    .debounce(for: .milliseconds(500), scheduler: DispatchQueue.main)
    .removeDuplicates()
    .sink { searchTerm in
        // Perform search
    }

Benefit: Reduces API calls while user is typing

2. Request Timeout

let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60

Benefit: Prevents hanging requests

3. Async/Await

func request<T: Decodable>(endpoint: APIConstants.Endpoint) async throws -> T {
    let (data, response) = try await session.data(for: request)
    return try decoder.decode(T.self, from: data)
}

Benefit: Clean, readable asynchronous code


🔒 Security Best Practices

✅ What We Do

  1. HTTPS Only: All requests use HTTPS
  2. Authentication: SHA1 hash-based auth
  3. No Hardcoded Secrets: API keys in constants (should be env vars)
  4. Timeout Protection: Request timeouts configured
  5. Error Handling: Proper error messages

⚠️ Production Recommendations

  1. Environment Variables: Move API keys to environment
  2. Certificate Pinning: Pin API certificates
  3. Rate Limiting: Implement client-side rate limiting
  4. Caching: Cache responses to reduce API calls
  5. Analytics: Track API errors and performance

📝 API Limits & Guidelines

Rate Limits

  • Free Tier: Reasonable use policy
  • No Hard Limits: But be respectful
  • Recommended: Cache responses when possible

Best Practices

  1. Cache Results: Don't fetch same data repeatedly
  2. Batch Requests: Combine when possible
  3. Handle Errors: Gracefully handle failures
  4. User Agent: Always include User-Agent header
  5. Attribution: Credit PodcastIndex in your app

🧪 Testing API Integration

Manual Testing

# Test authentication
curl -X GET \
  'https://api.podcastindex.org/api/1.0/podcasts/trending?max=5' \
  -H 'X-Auth-Key: YOUR_KEY' \
  -H 'X-Auth-Date: 1716163200' \
  -H 'Authorization: YOUR_HASH' \
  -H 'User-Agent: iPodcast/1.0'

Unit Testing

// Mock NetworkService
class MockNetworkService: NetworkService {
    var mockResponse: Any?
    
    override func request<T: Decodable>(endpoint: APIConstants.Endpoint) async throws -> T {
        return mockResponse as! T
    }
}

// Test Repository
func testGetTrendingPodcasts() async throws {
    let mockService = MockNetworkService()
    mockService.mockResponse = PodcastResponse(feeds: [...])
    
    let repository = PodcastRepository(networkService: mockService)
    let podcasts = try await repository.getTrendingPodcasts()
    
    XCTAssertEqual(podcasts.count, 5)
}

🐛 Common Issues & Solutions

Issue 1: "Invalid API Key"

Cause: Wrong API key or not set

Solution:

  1. Check APIConstants.swift
  2. Verify credentials from PodcastIndex
  3. Ensure no extra spaces

Issue 2: "Authorization Failed"

Cause: Incorrect SHA1 hash

Solution:

  1. Verify hash generation logic
  2. Check timestamp format
  3. Ensure correct concatenation order

Issue 3: "No Results"

Cause: Search term too specific or no matches

Solution:

  1. Try broader search terms
  2. Check API status
  3. Verify internet connection

Issue 4: "Decoding Error"

Cause: API response structure changed

Solution:

  1. Check API documentation
  2. Update DTO models
  3. Add optional fields

📚 Additional Resources

Official Documentation

Useful Links


🔄 API Version

Current Version: 1.0

Base URL: https://api.podcastindex.org/api/1.0

Last Updated: May 2026


📞 Support

For API issues:


Happy API Integration! 🚀