Efficient YouTube search API service without API key requirements. Built with FastAPI, yt-dlp, and modern Python tooling.
- 🔍 Search YouTube - Search for videos without API keys
- 📊 Detailed Metadata - Get comprehensive video information including formats, audio options, and more
- 🎵 Music-Optimized - Extracts audio-only formats perfect for music applications
- 🔒 Age-Restriction Bypass - Automatically handles age-restricted content using browser cookies
- ⚡ Fast & Efficient - Built with FastAPI and async support
- 💾 Smart Caching - In-memory caching to reduce redundant requests
- 🐳 Docker Ready - Multi-stage Docker builds for production deployment
- 📝 Type Safe - Full type hints with mypy validation
- ✅ Well Tested - Comprehensive test suite with pytest
- 📖 Auto Documentation - OpenAPI/Swagger docs at
/docs
- Python 3.13+
- FastAPI - Modern web framework
- yt-dlp - YouTube data extraction
- uvicorn - ASGI server
- uv - Fast Python package manager
- ruff - Linting and formatting
- mypy - Static type checking
- pytest - Testing framework
# Install dependencies
uv sync
# Run the development server
uv run uvicorn app.main:app --reload
# Visit http://localhost:8000/docs for interactive API documentation# Build and run with Docker Compose
docker-compose up --build
# Visit http://localhost:8000/docs# Build image
docker build -t searchy .
# Run container
docker run -p 8000:8000 searchyGET /search?q={query}&limit={limit}Parameters:
q(required): Search querylimit(optional): Number of results (1-50, default: 10)no_cache(optional): Skip cache and force fresh results (default: false)
Example:
curl "http://localhost:8000/search?q=python%20tutorial&limit=5"Response:
{
"query": "python tutorial",
"results": [
{
"video_id": "xyz123",
"title": "Python Tutorial for Beginners",
"url": "https://www.youtube.com/watch?v=xyz123",
"duration": 3600,
"view_count": 1000000,
"like_count": 50000,
"channel": "Tech Channel",
"channel_id": "UCxyz",
"upload_date": "20250101",
"description": "Learn Python...",
"thumbnail": "https://...",
"categories": ["Education"],
"tags": ["python", "tutorial"]
}
],
"count": 5,
"timestamp": "2025-01-01T12:00:00"
}GET /video/{video_id}Parameters:
video_id(required): YouTube video IDno_cache(optional): Skip cache and force fresh results
Example:
curl "http://localhost:8000/video/dQw4w9WgXcQ"Response:
{
"video_id": "dQw4w9WgXcQ",
"title": "Video Title",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"duration": 212,
"formats": [...],
"audio_only_formats": [...],
"best_audio_format": {
"format_id": "140",
"ext": "m4a",
"abr": 128,
"acodec": "mp4a.40.2"
}
}GET /audio/{video_id}Parameters:
video_id(required): YouTube video IDno_cache(optional): Skip cache and force fresh results (default: false)
Description: Returns direct audio stream URL optimized for music playback. Perfect for Discord bots and music applications. URLs typically expire in ~6 hours.
Example:
curl "http://localhost:8000/audio/dQw4w9WgXcQ"Response:
{
"video_id": "dQw4w9WgXcQ",
"title": "Rick Astley - Never Gonna Give You Up",
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"duration": 213,
"channel": "Rick Astley",
"thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
"audio_format": {
"format_id": "140",
"url": "https://rr2---sn-uxv-8ovl.googlevideo.com/videoplayback?...",
"ext": "m4a",
"acodec": "mp4a.40.2",
"abr": 128.0,
"filesize": 3452345,
"quality": "medium"
},
"url_expires_in": 21600,
"timestamp": "2025-01-17T12:00:00Z"
}Note: The audio_format.url is a direct link to the audio stream that can be used immediately for playback. Cache TTL is 60 seconds due to URL expiration.
GET /health# Get cache statistics
GET /cache/stats
# Clear cache
DELETE /cache# Install dependencies including dev tools
uv sync --all-extras
# Install pre-commit hooks
uv run pre-commit install# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=app
# Run only unit tests (skip integration)
uv run pytest -m "not integration"# Format code
uv run ruff format .
# Lint code
uv run ruff check .
# Fix linting issues automatically
uv run ruff check --fix .
# Type check
uv run mypy appuv run pre-commit run --all-filessearchy/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI app & routes
│ ├── models.py # Pydantic models
│ ├── services/
│ │ ├── __init__.py
│ │ └── youtube.py # yt-dlp wrapper
│ └── utils/
│ ├── __init__.py
│ └── cache.py # Caching utility
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Pytest fixtures
│ └── test_api.py # API tests
├── pyproject.toml # Project config & dependencies
├── .pre-commit-config.yaml # Pre-commit hooks
├── Dockerfile # Multi-stage Docker build
├── docker-compose.yml # Docker Compose config
└── README.md
Currently, the service works without configuration. Future additions:
CACHE_TTL- Cache time-to-live in seconds (default: 300)MAX_SEARCH_RESULTS- Maximum search results allowed (default: 50)
CORS is enabled for all origins by default. For production, configure allowed origins in app/main.py:
app.add_middleware(
CORSMiddleware,
allow_origins=["https://yourdomain.com"], # Update this
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)The service automatically handles age-restricted videos by extracting cookies from installed browsers. It tries browsers in this order:
- Chrome (default)
- Firefox
- Edge
- Safari
- Opera
- Brave
Requirements: You must be logged into YouTube in at least one of these browsers for age-restricted content to work.
If no browser cookies are available, the service will still work but may skip age-restricted videos.
The included Dockerfile uses multi-stage builds for optimal image size and security:
- Non-root user execution
- Minimal runtime dependencies
- Health checks included
- Consider adding Redis for distributed caching in multi-instance deployments
- Set up rate limiting for production use
- Configure proper CORS origins
- Use a reverse proxy (nginx, Caddy) for SSL/TLS
- Monitor yt-dlp updates as YouTube may change
MIT License - See LICENSE file for details
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request