Raw agent skill file — point your OpenClaw agent here.
Endpoint, auth, modes, and responses.
Step-by-step integration guide.
YTSkills integrates with OpenClaw through a skill.md file. Your agent reads it once to learn the API, then can process YouTube videos on demand from any channel — WhatsApp, Telegram, Slack, Discord, and more.
https://ytskills.com/skill.md~/.config/ytskills/credentials.jsonWhen your OpenClaw agent processes a YouTube video through YTSkills, the generated skill is saved as a markdown file in your agent's workspace. Skills are saved with descriptive names based on the video topic (e.g., react-hooks-tutorial.md) so your agent can reference them later.
You can also call the YTSkills API directly from scripts or custom tooling:
$ curl -H "Authorization: Bearer yt_your_key_here" \ https://ytskills.com/api/audio/dQw4w9WgXcQ
# YTSkills Skill Documentation
YTSkills is a service that allows AI agents to learn and improve their knowledge base and understanding of the world through YouTube videos. When a user shares a YouTube link and asks you to "learn from this," YTSkills extracts the content (audio or video) and generates a structured SKILL.md file that you can save and reference later.
## ⚠️ IMPORTANT: API Key Required
**You must obtain a valid API key from ytskills.com to use this service.** This prevents abuse and keeps request quotas fair. The process takes barely 2 minutes:
1. Visit ytskills.com and create an account.
2. Go to the API key page and click "Generate API Key".
3. Copy the API key and give it to the agent.
Save your API key in your `AGENT.md` for easy access:
```
## YTSkills Credentials
- API Key: yt_abc123...
- Base URL: https://ytskills.com/api
```
If the key is ever lost, you can regenerate a new one from the API key page at ytskills.com.
## Base URL
```
https://ytskills.com/api
```
## Authentication
All endpoints require a Bearer token in the Authorization header:
```
Authorization: Bearer YOUR_API_KEY
```
## Pricing & Credits
YTSkills uses a credit-based system. Each API call consumes credits based on the mode and the duration of the video.
### Plans
| Plan | Price | Credits | Audio | Video |
|------|-------|---------|-------|-------|
| Free | $0/mo | 50 credits | 1 credit/audio min | Audio-only (no video) |
| Pro | Coming Soon | 2,000 credits | 1 credit/audio min | 5 credits/video min |
| Max | Coming Soon | 4,000 credits | 1 credit/audio min | 5 credits/video min |
**Key details:**
- Current launch mode: Free tier is active. Pro/Max and video mode are coming soon.
- Credits reset monthly
- Video mode is currently coming soon during free-tier launch
- Credit cost is calculated based on the duration of the YouTube video, **rounded up to the nearest minute**
- Example: a 12.3-minute video costs 13 credits in audio mode and 65 credits in video mode
## Usage Flow
### How It Works
1. **User** sends you a YouTube link along with a message like "learn from this" or "study this video."
2. **You** extract the video ID from the YouTube URL.
3. **You** call the YTSkills API with the chosen mode (`audio` or `video`).
4. **The API** processes the video content through Gemini 3 Flash with a specialized system prompt.
5. **The API** returns a generated SKILL.md file along with metadata.
6. **You** save the generated skill file to an appropriate location with a descriptive name (e.g., `react-hooks-tutorial.md`) so you and the user can easily identify and reference it later.
### Extracting the Video ID
The video ID can be extracted from standard YouTube URL formats:
- `https://www.youtube.com/watch?v=VIDEO_ID`
- `https://youtu.be/VIDEO_ID`
- `https://www.youtube.com/embed/VIDEO_ID`
- `https://youtube.com/shorts/VIDEO_ID`
Extract only the video ID portion (the alphanumeric string, typically 11 characters).
## API Endpoint
### Generate Skill from YouTube Video
```http
GET /{mode}/{video-id}
Authorization: Bearer YOUR_API_KEY
```
**Path Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `mode` | string | Yes | Processing mode: `audio` or `video` |
| `video-id` | string | Yes | The YouTube video ID extracted from the URL |
**Modes:**
- `audio` — Extracts and processes the audio track only. Available on all plans (including Free). Costs 1 credit per minute of video duration.
- `video` — Processes both the video and audio content for richer output. **Currently coming soon.**
**Response (success):**
```json
{
"skill": "# Skill Title\n\nGenerated skill content in markdown format...",
"metadata": {
"video_id": "dQw4w9WgXcQ",
"title": "Video Title",
"duration_minutes": 12,
"mode": "audio",
"credits_used": 12,
"credits_remaining": 38
}
}
```
The `skill` field contains the full generated SKILL.md content in markdown format. The `metadata` field contains information about the processed video and credit usage.
**Response (insufficient credits):**
```json
{
"error": "Insufficient credits. This video requires 50 credits but you only have 38 remaining.",
"credits_required": 50,
"credits_remaining": 38,
"upgrade_url": "https://ytskills.com/pricing"
}
```
**Response (video mode on free plan):**
```json
{
"error": "Video mode is coming soon. Free-tier launch currently supports audio mode only.",
"upgrade_url": "https://ytskills.com/pricing"
}
```
## Storing Generated Skills
When you receive a skill from the API, save it with a **descriptive, human-readable name** based on the video's topic (e.g., `react-hooks-tutorial.md`). This makes skills easy to find, remember, and use as a knowledge base. If the user specifies a particular name or location, use that instead.
## Credential Storage
Store your credentials at:
```
~/.config/ytskills/credentials.json
```
```json
{
"api_key": "yt_your_api_key_here"
}
```
## Error Responses
All errors follow this format:
```json
{
"error": "Error message here"
}
```
Common status codes:
- `400` — Bad request (invalid video ID or mode)
- `401` — Unauthorized (missing or invalid API key)
- `402` — Payment required (insufficient credits)
- `403` — Forbidden (video mode on free plan)
- `404` — Not found (video does not exist or is unavailable)
- `429` — Rate limited (too many requests)
- `500` — Server error
## Rate Limits
| Plan | Requests per Minute |
|------|---------------------|
| Free | 4 RPM |
| Pro | 12 RPM |
| Max | 30 RPM |
## Resilience & Retry Logic
**IMPORTANT:** Network errors and rate limits happen. Your bot MUST implement retry logic to handle transient failures gracefully. Never give up on a single failure.
### Retry Strategy
Use exponential backoff with jitter:
1. On failure, wait `base_delay * (2 ^ attempt)` seconds
2. Add random jitter (0-1 second) to prevent thundering herd
3. Retry up to 5 times before giving up
4. On `429` (rate limit), respect `Retry-After` header if present
### Retryable vs Non-Retryable Errors
**Always retry:**
- Network errors (connection refused, timeout, SSL errors)
- `429` — Rate limited
- `500`, `502`, `503`, `504` — Server errors
**Don't retry:**
- `400` — Bad request (fix your input)
- `401` — Unauthorized (fix your API key)
- `402` — Payment required (user needs more credits)
- `403` — Forbidden (user needs to upgrade plan)
- `404` — Not found (video doesn't exist)
## Example Agent Implementation
```python
import requests
import time
import random
import re
import os
import json
BASE_URL = "https://ytskills.com/api"
SKILLS_DIR = os.path.expanduser("~/.config/ytskills/skills")
CREDS_PATH = os.path.expanduser("~/.config/ytskills/credentials.json")
def get_api_key():
"""Load API key from credentials file."""
if os.path.exists(CREDS_PATH):
with open(CREDS_PATH) as f:
return json.load(f)["api_key"]
return None
def extract_video_id(url):
"""Extract video ID from a YouTube URL."""
patterns = [
r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/embed/|youtube\.com/shorts/)([a-zA-Z0-9_-]{11})',
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
return match.group(1)
return None
def retry_request(method, url, max_retries=5, base_delay=1, **kwargs):
"""Make HTTP request with exponential backoff retry."""
for attempt in range(max_retries):
try:
response = method(url, timeout=60, **kwargs)
# Success or non-retryable error
if response.status_code < 500 and response.status_code != 429:
return response
# Retryable error - wait and retry
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Request failed ({response.status_code}), retrying in {delay:.1f}s...")
time.sleep(delay)
continue
return response
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
print(f"Network error ({type(e).__name__}), retrying in {delay:.1f}s...")
time.sleep(delay)
continue
raise
return response
def learn_from_video(youtube_url, mode="audio"):
"""Process a YouTube video and save the generated skill."""
api_key = get_api_key()
if not api_key:
print("No API key found. Visit ytskills.com to get one.")
return None
video_id = extract_video_id(youtube_url)
if not video_id:
print("Could not extract video ID from URL.")
return None
headers = {
"Authorization": f"Bearer {api_key}",
}
print(f"Processing video {video_id} in {mode} mode...")
response = retry_request(
requests.get,
f"{BASE_URL}/{mode}/{video_id}",
headers=headers,
)
if response.status_code != 200:
error = response.json()
print(f"Error: {error.get('error', 'Unknown error')}")
return None
data = response.json()
metadata = data["metadata"]
# Save the skill file with a descriptive name derived from the video title
os.makedirs(SKILLS_DIR, exist_ok=True)
title_slug = re.sub(r'[^a-z0-9]+', '-', metadata["title"].lower()).strip('-')
skill_path = os.path.join(SKILLS_DIR, f"{title_slug}.md")
with open(skill_path, "w") as f:
f.write(data["skill"])
print(f"Skill saved to {skill_path}")
print(f"Video: {metadata['title']} ({metadata['duration_minutes']} min)")
print(f"Credits used: {metadata['credits_used']} | Remaining: {metadata['credits_remaining']}")
return skill_path
if __name__ == "__main__":
url = input("YouTube URL: ")
mode = input("Mode (audio/video): ").strip() or "audio"
learn_from_video(url, mode)
```
## Choosing a Mode
**Always ask the user whether they want audio or video mode** before making the API call. Do NOT assume a mode on their behalf.
The only exceptions where you may skip asking:
- The user already specified the mode in their message (e.g., "learn from this video using audio mode")
- The user has previously told you to default to a specific mode for all future requests
## Tips for Agents
1. **Check remaining credits** — if the response indicates low credits, warn the user so they can upgrade or be selective about which videos to process.
2. **Save skills immediately** — don't lose generated content; write it to disk as soon as you receive it.
3. **Reference saved skills** — when a user asks about a topic you've previously learned from a video, check your saved skills first.
4. **Tell the user what you learned** — after processing a video, summarize the key takeaways so the user knows the skill was generated successfully.