free-resource
Search and retrieve royalty-free media from Pixabay (images/videos), Freesound (audio effects), and Jamendo (music/BGM). Use when the user needs to find stock photos, illustrations, vectors, videos, sound effects, or background music, download media, or query media libraries with filters.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-free-resource
Repository
Skill path: skills/darknoah/free-resource
Search and retrieve royalty-free media from Pixabay (images/videos), Freesound (audio effects), and Jamendo (music/BGM). Use when the user needs to find stock photos, illustrations, vectors, videos, sound effects, or background music, download media, or query media libraries with filters.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: Creative Commons 0".
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install free-resource into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding free-resource to shared team environments
- Use free-resource for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: free-resource
description: "Search and retrieve royalty-free media from Pixabay (images/videos), Freesound (audio effects), and Jamendo (music/BGM). Use when the user needs to find stock photos, illustrations, vectors, videos, sound effects, or background music, download media, or query media libraries with filters."
---
# Free Resource
Search and download royalty-free images, videos, sound effects, and music from Pixabay, Freesound, and Jamendo.
## Quick Start
```bash
# 1. Copy config template and fill in your API keys
cp config.example.json config.json
# 2. Edit config.json with your API keys
# 3. Use without passing API keys
bun ./scripts/jamendo.ts search --query "background" --limit 5
bun ./scripts/freesound.ts search --query "piano"
bun ./scripts/pixabay.ts search-images --query "nature"
```
## Configuration
API keys are stored in `config.json`. Copy `config.example.json` and fill in your keys:
```json
{
"pixabay": {
"api_key": "YOUR_PIXABAY_API_KEY"
},
"freesound": {
"api_token": "YOUR_FREESOUND_TOKEN"
},
"jamendo": {
"client_id": "YOUR_JAMENDO_CLIENT_ID"
}
}
```
### Get API Keys
| Platform | Type | Get API Key |
|----------|------|-------------|
| Pixabay | Images/Videos | https://pixabay.com/accounts/register/ |
| Freesound | Audio Effects | https://freesound.org/apiv2/apply |
| Jamendo | Music/BGM | https://devportal.jamendo.com/ |
### API Key Priority
1. **CLI flag**: `--key`, `--token`, or `--client-id`
2. **Environment variable**: `PIXABAY_API_KEY`, `FREESOUND_API_TOKEN`, `JAMENDO_CLIENT_ID`
3. **Config file**: `config.json`
---
## Pixabay (Images & Videos)
### Search Images
```bash
bun ./scripts/pixabay.ts search-images --query "yellow flowers" --image-type photo --orientation horizontal --per-page 5
```
Flags: `--query`, `--id`, `--lang`, `--image-type` (all|photo|illustration|vector), `--orientation` (all|horizontal|vertical), `--category`, `--colors` (comma-separated), `--min-width`, `--min-height`, `--editors-choice`, `--safesearch`, `--order` (popular|latest), `--page`, `--per-page` (5-200), `--output` (save to file).
### Search Videos
```bash
bun ./scripts/pixabay.ts search-videos --query "ocean waves" --video-type film --per-page 5
```
### Download
```bash
bun ./scripts/pixabay.ts download --url "https://pixabay.com/get/..." --output "/path/to/save.jpg"
```
---
## Freesound (Audio Effects)
### Search Sounds
```bash
bun ./scripts/freesound.ts search --query "piano note" --page-size 10
```
Flags: `--query`, `--filter`, `--sort`, `--fields`, `--page`, `--page-size` (max 150), `--group-by-pack`, `--output`.
### Filter Examples
```bash
bun ./scripts/freesound.ts search --query "drum" --filter "duration:[0 TO 2]"
bun ./scripts/freesound.ts search --query "ambient" --filter "type:wav"
bun ./scripts/freesound.ts search --query "explosion" --sort downloads_desc
```
### Get Sound Details
```bash
bun ./scripts/freesound.ts get --id 12345 --fields id,name,previews,duration
```
### Download Preview
```bash
bun ./scripts/freesound.ts download --id 12345 --output ./sound.mp3
```
---
## Jamendo (Music & BGM)
### Search Music
```bash
bun ./scripts/jamendo.ts search --query "rock" --limit 10
```
Flags: `--query`, `--tags`, `--fuzzytags`, `--artist-name`, `--album-name`, `--order`, `--limit` (max 200), `--offset`, `--output`.
### Music Attribute Filters
```bash
# Instrumental background music
bun ./scripts/jamendo.ts search --query "background" --vocalinstrumental instrumental
# Search by tags (AND logic)
bun ./scripts/jamendo.ts search --tags "electronic+chill" --order popularity_total_desc
# Search by speed
bun ./scripts/jamendo.ts search --query "energetic" --speed high+veryhigh
```
### Get Track Details
```bash
bun ./scripts/jamendo.ts track --id 12345 --include musicinfo,stats
```
### Download Track
```bash
bun ./scripts/jamendo.ts download --id 12345 --output ./music.mp3
```
---
## API Reference
For full parameter tables, response field descriptions, and rate limit details, see `./references/api_reference.md`.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/jamendo.ts
```typescript
#!/usr/bin/env bun
/**
* Jamendo API CLI – search and download royalty-free music.
*
* Usage:
* bun ./scripts/jamendo.ts search --query "rock" --limit 10
* bun ./scripts/jamendo.ts track --id 12345
* bun ./scripts/jamendo.ts album --id 12345
* bun ./scripts/jamendo.ts artist --id 12345
* bun ./scripts/jamendo.ts download --id 12345 --output ./music.mp3
*/
import * as path from "path";
import * as fs from "fs";
const BASE_URL = "https://api.jamendo.com/v3.0";
const CONFIG_FILE = path.join(import.meta.dir, "..", "config.json");
// ── helpers ──────────────────────────────────────────────────────────────
function loadConfig(): Record<string, any> {
if (fs.existsSync(CONFIG_FILE)) {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
} catch (e) {
return {};
}
}
return {};
}
function getClientId(args: Record<string, string | undefined>): string {
// Priority: CLI arg > env var > config file
const clientId = args["--client-id"] ?? args["--key"] ?? process.env.JAMENDO_CLIENT_ID ?? loadConfig()?.jamendo?.client_id;
if (!clientId) {
console.error(
"Error: Client ID required. Use --client-id, set JAMENDO_CLIENT_ID env var, or add to config.json."
);
process.exit(1);
}
return clientId;
}
function parseArgs(argv: string[]): {
command: string;
flags: Record<string, string>;
} {
const command = argv[0] ?? "";
const flags: Record<string, string> = {};
for (let i = 1; i < argv.length; i++) {
const arg = argv[i];
if (arg.startsWith("--") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
flags[arg] = argv[++i];
} else if (arg.startsWith("--")) {
flags[arg] = "true";
}
}
return { command, flags };
}
async function apiRequest(
endpoint: string,
params: Record<string, string | number | undefined>,
clientId: string
): Promise<any> {
const qs = new URLSearchParams();
qs.set("client_id", clientId);
qs.set("format", "json");
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null && v !== "") {
qs.set(k, String(v));
}
}
const url = `${BASE_URL}${endpoint}/?${qs.toString()}`;
const resp = await fetch(url, {
headers: { "User-Agent": "JamendoCLI/0.1" },
});
if (!resp.ok) {
const body = await resp.text();
console.error(`HTTP ${resp.status}: ${body}`);
process.exit(1);
}
const data = await resp.json();
// Jamendo returns status in headers.status
if (data.headers?.status !== "success") {
console.error(`API Error: ${data.headers?.error_message || "Unknown error"}`);
process.exit(1);
}
return data;
}
async function downloadFile(url: string, output: string): Promise<void> {
const resp = await fetch(url);
if (!resp.ok) {
console.error(`Download failed – HTTP ${resp.status}`);
process.exit(1);
}
const buf = await resp.arrayBuffer();
await Bun.write(output, new Uint8Array(buf));
console.error(`Downloaded: ${output}`);
}
// ── commands ─────────────────────────────────────────────────────────────
async function searchTracks(flags: Record<string, string>) {
const clientId = getClientId(flags);
const params: Record<string, string | number | undefined> = {};
// Pagination
if (flags["--limit"]) params.limit = Number(flags["--limit"]);
if (flags["--offset"]) params.offset = Number(flags["--offset"]);
if (flags["--fullcount"]) params.fullcount = "true";
// Search parameters
if (flags["--query"]) params.search = flags["--query"];
if (flags["--name"]) params.name = flags["--name"];
if (flags["--namesearch"]) params.namesearch = flags["--namesearch"];
if (flags["--tags"]) params.tags = flags["--tags"];
if (flags["--fuzzytags"]) params.fuzzytags = flags["--fuzzytags"];
// Filters
if (flags["--artist-id"]) params.artist_id = flags["--artist-id"];
if (flags["--artist-name"]) params.artist_name = flags["--artist-name"];
if (flags["--album-id"]) params.album_id = flags["--album-id"];
if (flags["--album-name"]) params.album_name = flags["--album-name"];
if (flags["--type"]) params.type = flags["--type"];
if (flags["--featured"]) params.featured = "true";
// Music attributes
if (flags["--vocalinstrumental"]) params.vocalinstrumental = flags["--vocalinstrumental"];
if (flags["--acousticelectric"]) params.acousticelectric = flags["--acousticelectric"];
if (flags["--speed"]) params.speed = flags["--speed"];
if (flags["--lang"]) params.lang = flags["--lang"];
if (flags["--gender"]) params.gender = flags["--gender"];
// Date/duration range
if (flags["--datebetween"]) params.datebetween = flags["--datebetween"];
if (flags["--durationbetween"]) params.durationbetween = flags["--durationbetween"];
// Audio format
if (flags["--audioformat"]) params.audioformat = flags["--audioformat"];
if (flags["--imagesize"]) params.imagesize = flags["--imagesize"];
// Sorting
if (flags["--order"]) params.order = flags["--order"];
if (flags["--boost"]) params.boost = flags["--boost"];
if (flags["--groupby"]) params.groupby = flags["--groupby"];
// Include extra info
if (flags["--include"]) params.include = flags["--include"];
// License filters
if (flags["--ccnc"]) params.ccnc = "true";
if (flags["--ccsa"]) params.ccsa = "true";
if (flags["--ccnd"]) params.ccnd = "true";
const data = await apiRequest("/tracks", params, clientId);
const header = data.results_count
? `Found ${data.results_fullcount ?? data.results_count} tracks (showing ${data.results_count})`
: `Found ${data.results?.length ?? 0} tracks`;
console.error(header);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function getTrack(flags: Record<string, string>) {
const clientId = getClientId(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const params: Record<string, string | number | undefined> = {
id,
include: flags["--include"] || "musicinfo,stats,licenses",
};
if (flags["--audioformat"]) params.audioformat = flags["--audioformat"];
const data = await apiRequest("/tracks", params, clientId);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Track info saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function searchAlbums(flags: Record<string, string>) {
const clientId = getClientId(flags);
const params: Record<string, string | number | undefined> = {};
if (flags["--limit"]) params.limit = Number(flags["--limit"]);
if (flags["--offset"]) params.offset = Number(flags["--offset"]);
if (flags["--fullcount"]) params.fullcount = "true";
if (flags["--id"]) params.id = flags["--id"];
if (flags["--name"]) params.name = flags["--name"];
if (flags["--namesearch"]) params.namesearch = flags["--namesearch"];
if (flags["--artist-id"]) params.artist_id = flags["--artist-id"];
if (flags["--artist-name"]) params.artist_name = flags["--artist-name"];
if (flags["--datebetween"]) params.datebetween = flags["--datebetween"];
if (flags["--type"]) params.type = flags["--type"];
if (flags["--imagesize"]) params.imagesize = flags["--imagesize"];
if (flags["--order"]) params.order = flags["--order"];
const data = await apiRequest("/albums", params, clientId);
console.error(`Found ${data.results?.length ?? 0} albums`);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function searchArtists(flags: Record<string, string>) {
const clientId = getClientId(flags);
const params: Record<string, string | number | undefined> = {};
if (flags["--limit"]) params.limit = Number(flags["--limit"]);
if (flags["--offset"]) params.offset = Number(flags["--offset"]);
if (flags["--fullcount"]) params.fullcount = "true";
if (flags["--id"]) params.id = flags["--id"];
if (flags["--name"]) params.name = flags["--name"];
if (flags["--namesearch"]) params.namesearch = flags["--namesearch"];
if (flags["--datebetween"]) params.datebetween = flags["--datebetween"];
if (flags["--hasimage"]) params.hasimage = "true";
if (flags["--order"]) params.order = flags["--order"];
const data = await apiRequest("/artists", params, clientId);
console.error(`Found ${data.results?.length ?? 0} artists`);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function getArtistTracks(flags: Record<string, string>) {
const clientId = getClientId(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const params: Record<string, string | number | undefined> = { artist_id: id };
if (flags["--limit"]) params.limit = Number(flags["--limit"]);
if (flags["--order"]) params.order = flags["--order"];
if (flags["--audioformat"]) params.audioformat = flags["--audioformat"];
const data = await apiRequest("/tracks", params, clientId);
console.error(`Found ${data.results?.length ?? 0} tracks`);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function downloadTrack(flags: Record<string, string>) {
const clientId = getClientId(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
if (!flags["--output"]) {
console.error("Error: --output is required for download");
process.exit(1);
}
// Get track info with download URL
const params: Record<string, string | undefined> = {
id,
audioformat: flags["--format"] || "mp32",
audiodlformat: flags["--format"] || "mp32",
};
const data = await apiRequest("/tracks", params, clientId);
if (!data.results?.[0]) {
console.error("Error: Track not found");
process.exit(1);
}
const track = data.results[0];
if (!track.audiodownload_allowed) {
console.error("Error: Download not allowed for this track");
console.error(`Stream URL: ${track.audio}`);
process.exit(1);
}
console.error(`Downloading: ${track.name} by ${track.artist_name}`);
await downloadFile(track.audiodownload, flags["--output"]);
}
async function streamUrl(flags: Record<string, string>) {
const clientId = getClientId(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const params: Record<string, string | undefined> = {
id,
audioformat: flags["--format"] || "mp32",
};
const data = await apiRequest("/tracks", params, clientId);
if (!data.results?.[0]) {
console.error("Error: Track not found");
process.exit(1);
}
const track = data.results[0];
console.log(track.audio);
}
// ── help ─────────────────────────────────────────────────────────────────
function printHelp() {
console.log(`Jamendo API CLI – search and download royalty-free music
Commands:
search Search for music tracks
track Get track details by ID
album Search/get album details
artist Search/get artist details
artist-tracks Get all tracks by an artist
download Download a track by ID
stream Get streaming URL for a track
Common flags:
--client-id Jamendo Client ID (or set JAMENDO_CLIENT_ID env var)
--output / -o Save JSON output to file
Search flags:
--query Free text search (track, album, artist, tags)
--name Exact name match
--namesearch Fuzzy name search
--tags Tag search (AND logic, use + to separate)
--fuzzytags Fuzzy tag search (OR logic)
--artist-id Filter by artist ID
--artist-name Filter by artist name
--album-id Filter by album ID
--limit Results per page (max 200, default 10)
--offset Pagination offset
--order Sort order (see below)
--include Extra info: musicinfo, stats, licenses, lyrics
Music attribute filters:
--vocalinstrumental vocal | instrumental
--acousticelectric acoustic | electric
--speed verylow | low | medium | high | veryhigh
--gender male | female
--lang Lyrics language (2-letter code)
--featured Featured tracks only
Date/Duration filters:
--datebetween Date range: yyyy-mm-dd_yyyy-mm-dd
--durationbetween Duration range in seconds: from_to
Audio formats:
--audioformat mp31 (96kbps) | mp32 (192kbps VBR) | ogg | flac
Sort options (add _asc or _desc):
relevance (default), popularity_week, popularity_month, popularity_total,
downloads_week, downloads_month, downloads_total, listens_*, name, releasedate
Examples:
# Search for rock music
bun ./scripts/jamendo.ts search --query "rock" --limit 10
# Search instrumental background music
bun ./scripts/jamendo.ts search --query "background" --vocalinstrumental instrumental
# Search by tags
bun ./scripts/jamendo.ts search --tags "electronic+chill" --order popularity_total_desc
# Get track details
bun ./scripts/jamendo.ts track --id 12345 --include musicinfo,stats
# Download a track
bun ./scripts/jamendo.ts download --id 12345 --output ./music.mp3
# Get artist's tracks
bun ./scripts/jamendo.ts artist-tracks --id 421168 --limit 20
# Search albums
bun ./scripts/jamendo.ts album --artist-name "Artist Name"
# Get FLAC format
bun ./scripts/jamendo.ts search --query "jazz" --audioformat flac`);
}
// ── main ─────────────────────────────────────────────────────────────────
const rawArgs = process.argv.slice(2);
if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "-h") {
printHelp();
process.exit(0);
}
const { command, flags } = parseArgs(rawArgs);
switch (command) {
case "search":
await searchTracks(flags);
break;
case "track":
await getTrack(flags);
break;
case "album":
await searchAlbums(flags);
break;
case "artist":
await searchArtists(flags);
break;
case "artist-tracks":
await getArtistTracks(flags);
break;
case "download":
await downloadTrack(flags);
break;
case "stream":
await streamUrl(flags);
break;
default:
console.error(`Unknown command: ${command}`);
printHelp();
process.exit(1);
}
```
### scripts/freesound.ts
```typescript
#!/usr/bin/env bun
/**
* Freesound API CLI – search and download free sound effects.
*
* Usage:
* bun ./scripts/freesound.ts search --query "piano note"
* bun ./scripts/freesound.ts get --id 12345
* bun ./scripts/freesound.ts similar --id 12345
* bun ./scripts/freesound.ts download --id 12345 --output ./sound.mp3
*/
import * as path from "path";
import * as fs from "fs";
const BASE_URL = "https://freesound.org/apiv2";
const CONFIG_FILE = path.join(import.meta.dir, "..", "config.json");
// ── helpers ──────────────────────────────────────────────────────────────
function loadConfig(): Record<string, any> {
if (fs.existsSync(CONFIG_FILE)) {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
} catch (e) {
return {};
}
}
return {};
}
function getApiToken(args: Record<string, string | undefined>): string {
// Priority: CLI arg > env var > config file
const token = args["--token"] ?? args["--key"] ?? process.env.FREESOUND_API_TOKEN ?? loadConfig()?.freesound?.api_token;
if (!token) {
console.error(
"Error: API token required. Use --token, set FREESOUND_API_TOKEN env var, or add to config.json."
);
process.exit(1);
}
return token;
}
function parseArgs(argv: string[]): {
command: string;
flags: Record<string, string>;
} {
const command = argv[0] ?? "";
const flags: Record<string, string> = {};
for (let i = 1; i < argv.length; i++) {
const arg = argv[i];
if (arg.startsWith("--") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
flags[arg] = argv[++i];
} else if (arg.startsWith("--")) {
// boolean flag
flags[arg] = "true";
}
}
return { command, flags };
}
async function apiRequest(
endpoint: string,
params: Record<string, string | number | undefined>,
token: string
): Promise<any> {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null && v !== "") qs.set(k, String(v));
}
const url = `${BASE_URL}${endpoint}?${qs.toString()}`;
const resp = await fetch(url, {
headers: {
"Authorization": `Token ${token}`,
"User-Agent": "FreesoundCLI/0.1"
},
});
if (!resp.ok) {
const body = await resp.text();
console.error(`HTTP ${resp.status}: ${body}`);
process.exit(1);
}
return resp.json();
}
async function downloadFile(url: string, output: string, token?: string): Promise<void> {
const headers: Record<string, string> = {
"User-Agent": "FreesoundCLI/0.1"
};
if (token) {
headers["Authorization"] = `Token ${token}`;
}
const resp = await fetch(url, { headers });
if (!resp.ok) {
console.error(`Download failed – HTTP ${resp.status}`);
process.exit(1);
}
const buf = await resp.arrayBuffer();
await Bun.write(output, new Uint8Array(buf));
console.error(`Downloaded: ${output}`);
}
// ── commands ─────────────────────────────────────────────────────────────
async function searchSounds(flags: Record<string, string>) {
const token = getApiToken(flags);
const params: Record<string, string | number | undefined> = {};
if (flags["--query"]) params.query = flags["--query"];
if (flags["--filter"]) params.filter = flags["--filter"];
if (flags["--sort"]) params.sort = flags["--sort"];
if (flags["--fields"]) params.fields = flags["--fields"];
if (flags["--page"]) params.page = Number(flags["--page"]);
if (flags["--page-size"]) params.page_size = Number(flags["--page-size"]);
if (flags["--group-by-pack"]) params.group_by_pack = "1";
const data = await apiRequest("/search/", params, token);
console.error(
`Found ${data.count ?? 0} sounds (showing ${data.results?.length ?? 0})`
);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function getSound(flags: Record<string, string>) {
const token = getApiToken(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const params: Record<string, string | undefined> = {};
if (flags["--fields"]) params.fields = flags["--fields"];
const data = await apiRequest(`/sounds/${id}/`, params, token);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Sound info saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function getSimilar(flags: Record<string, string>) {
const token = getApiToken(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const params: Record<string, string | number | undefined> = {};
if (flags["--fields"]) params.fields = flags["--fields"];
if (flags["--page"]) params.page = Number(flags["--page"]);
if (flags["--page-size"]) params.page_size = Number(flags["--page-size"]);
const data = await apiRequest(`/sounds/${id}/similar/`, params, token);
console.error(
`Found ${data.count ?? 0} similar sounds`
);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function getComments(flags: Record<string, string>) {
const token = getApiToken(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
const data = await apiRequest(`/sounds/${id}/comments/`, {}, token);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Comments saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function downloadPreview(flags: Record<string, string>) {
const token = getApiToken(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
if (!flags["--output"]) {
console.error("Error: --output is required for download");
process.exit(1);
}
// First get sound info to retrieve preview URL
const sound = await apiRequest(`/sounds/${id}/`, { fields: "previews,name" }, token);
// Prefer HQ MP3, fallback to LQ MP3
const previewUrl = sound.previews?.["preview-hq-mp3"] || sound.previews?.["preview-lq-mp3"];
if (!previewUrl) {
console.error("Error: No preview URL available for this sound");
process.exit(1);
}
console.error(`Downloading: ${sound.name}`);
await downloadFile(previewUrl, flags["--output"]);
}
async function downloadSound(flags: Record<string, string>) {
const token = getApiToken(flags);
const id = flags["--id"];
if (!id) {
console.error("Error: --id is required");
process.exit(1);
}
if (!flags["--output"]) {
console.error("Error: --output is required for download");
process.exit(1);
}
// Get download URL
const data = await apiRequest(`/sounds/${id}/download/`, {}, token);
if (!data.download_link) {
console.error("Error: No download link returned. Note: OAuth2 may be required for original file downloads.");
process.exit(1);
}
await downloadFile(data.download_link, flags["--output"], token);
}
// ── help ─────────────────────────────────────────────────────────────────
function printHelp() {
console.log(`Freesound API CLI – search and download free sound effects
Commands:
search Search for sounds
get Get sound details by ID
similar Get similar sounds by ID
comments Get comments for a sound
download Download sound preview (high-quality MP3)
Common flags:
--token Freesound API token (or set FREESOUND_API_TOKEN env var)
--output / -o Save JSON output to file
Search flags:
--query Search term (supports +/- modifiers, phrases in quotes)
--filter Filter results (e.g., "duration:[0.1 TO 1.0]" "type:wav")
--sort Sort by: score (default), duration_desc, created_desc,
downloads_desc, rating_desc, etc.
--fields Comma-separated fields to return
(default: id,name,tags,username,license)
--page Page number (default: 1)
--page-size Results per page, max 150 (default: 15)
--group-by-pack Group results by pack (1/0)
Get/Similar/Comments flags:
--id Sound ID (required)
--fields Comma-separated fields to return
Download flags:
--id Sound ID (required)
--output Local file path to save (required)
Filter examples:
--filter "duration:[0.1 TO 1.0]"
--filter "type:wav"
--filter "tag:piano"
--filter "bpm:120"
--filter "license:Creative Commons 0"
Examples:
# Search for piano sounds
bun ./scripts/freesound.ts search --query "piano note" --page-size 10
# Search with filter
bun ./scripts/freesound.ts search --query "drum" --filter "duration:[0 TO 2]" --sort downloads_desc
# Get sound details
bun ./scripts/freesound.ts get --id 12345 --fields id,name,previews,duration
# Get similar sounds
bun ./scripts/freesound.ts similar --id 12345
# Download preview
bun ./scripts/freesound.ts download --id 12345 --output ./sound.mp3`);
}
// ── main ─────────────────────────────────────────────────────────────────
const rawArgs = process.argv.slice(2);
if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "-h") {
printHelp();
process.exit(0);
}
const { command, flags } = parseArgs(rawArgs);
switch (command) {
case "search":
await searchSounds(flags);
break;
case "get":
await getSound(flags);
break;
case "similar":
await getSimilar(flags);
break;
case "comments":
await getComments(flags);
break;
case "download":
await downloadPreview(flags);
break;
case "download-original":
await downloadSound(flags);
break;
default:
console.error(`Unknown command: ${command}`);
printHelp();
process.exit(1);
}
```
### scripts/pixabay.ts
```typescript
#!/usr/bin/env bun
/**
* Pixabay API CLI – search and download royalty-free images & videos.
*
* Usage:
* bun ./scripts/pixabay.ts search-images --query "yellow flowers" --image-type photo
* bun ./scripts/pixabay.ts search-videos --query "ocean waves"
* bun ./scripts/pixabay.ts download --url "https://..." --output "/path/to/file.jpg"
*/
import * as path from "path";
import * as fs from "fs";
const BASE_IMAGE_URL = "https://pixabay.com/api/";
const BASE_VIDEO_URL = "https://pixabay.com/api/videos/";
const CONFIG_FILE = path.join(import.meta.dir, "..", "config.json");
// ── helpers ──────────────────────────────────────────────────────────────
function loadConfig(): Record<string, any> {
if (fs.existsSync(CONFIG_FILE)) {
try {
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
} catch (e) {
return {};
}
}
return {};
}
function getApiKey(args: Record<string, string | undefined>): string {
// Priority: CLI arg > env var > config file
const key = args["--key"] ?? process.env.PIXABAY_API_KEY ?? loadConfig()?.pixabay?.api_key;
if (!key) {
console.error(
"Error: API key required. Use --key, set PIXABAY_API_KEY env var, or add to config.json."
);
process.exit(1);
}
return key;
}
function parseArgs(argv: string[]): {
command: string;
flags: Record<string, string>;
} {
const command = argv[0] ?? "";
const flags: Record<string, string> = {};
for (let i = 1; i < argv.length; i++) {
const arg = argv[i];
if (arg.startsWith("--") && i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
flags[arg] = argv[++i];
} else if (arg.startsWith("--")) {
// boolean flag
flags[arg] = "true";
}
}
return { command, flags };
}
async function apiRequest(
baseUrl: string,
params: Record<string, string | number>
): Promise<any> {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null && v !== "") qs.set(k, String(v));
}
const url = `${baseUrl}?${qs.toString()}`;
const resp = await fetch(url, {
headers: { "User-Agent": "PixabayCLI/0.1" },
});
// rate-limit info
const limit = resp.headers.get("X-RateLimit-Limit");
const remaining = resp.headers.get("X-RateLimit-Remaining");
const reset = resp.headers.get("X-RateLimit-Reset");
if (limit) {
console.error(`Rate limit: ${remaining}/${limit} remaining, resets in ${reset}s`);
}
if (!resp.ok) {
const body = await resp.text();
console.error(`HTTP ${resp.status}: ${body}`);
process.exit(1);
}
return resp.json();
}
async function downloadFile(url: string, output: string): Promise<void> {
const resp = await fetch(url);
if (!resp.ok) {
console.error(`Download failed – HTTP ${resp.status}`);
process.exit(1);
}
const buf = await resp.arrayBuffer();
await Bun.write(output, new Uint8Array(buf));
console.error(`Downloaded: ${output}`);
}
// ── commands ─────────────────────────────────────────────────────────────
async function searchImages(flags: Record<string, string>) {
const key = getApiKey(flags);
const params: Record<string, string | number> = { key };
if (flags["--query"]) params.q = flags["--query"];
if (flags["--id"]) params.id = flags["--id"];
if (flags["--lang"]) params.lang = flags["--lang"];
if (flags["--image-type"] && flags["--image-type"] !== "all")
params.image_type = flags["--image-type"];
if (flags["--orientation"] && flags["--orientation"] !== "all")
params.orientation = flags["--orientation"];
if (flags["--category"]) params.category = flags["--category"];
if (flags["--min-width"]) params.min_width = Number(flags["--min-width"]);
if (flags["--min-height"]) params.min_height = Number(flags["--min-height"]);
if (flags["--colors"]) params.colors = flags["--colors"];
if (flags["--editors-choice"]) params.editors_choice = "true";
if (flags["--safesearch"]) params.safesearch = "true";
if (flags["--order"] && flags["--order"] !== "popular")
params.order = flags["--order"];
if (flags["--page"]) params.page = Number(flags["--page"]);
if (flags["--per-page"]) {
const perPage = Number(flags["--per-page"]);
params.per_page = perPage < 5 ? 5 : perPage;
}
const data = await apiRequest(BASE_IMAGE_URL, params);
console.error(
`Found ${data.totalHits ?? 0} accessible images (total: ${data.total ?? 0})`
);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function searchVideos(flags: Record<string, string>) {
const key = getApiKey(flags);
const params: Record<string, string | number> = { key };
if (flags["--query"]) params.q = flags["--query"];
if (flags["--id"]) params.id = flags["--id"];
if (flags["--lang"]) params.lang = flags["--lang"];
if (flags["--video-type"] && flags["--video-type"] !== "all")
params.video_type = flags["--video-type"];
if (flags["--category"]) params.category = flags["--category"];
if (flags["--min-width"]) params.min_width = Number(flags["--min-width"]);
if (flags["--min-height"]) params.min_height = Number(flags["--min-height"]);
if (flags["--editors-choice"]) params.editors_choice = "true";
if (flags["--safesearch"]) params.safesearch = "true";
if (flags["--order"] && flags["--order"] !== "popular")
params.order = flags["--order"];
if (flags["--page"]) params.page = Number(flags["--page"]);
if (flags["--per-page"]) {
const perPage = Number(flags["--per-page"]);
params.per_page = perPage < 5 ? 5 : perPage;
}
const data = await apiRequest(BASE_VIDEO_URL, params);
console.error(
`Found ${data.totalHits ?? 0} accessible videos (total: ${data.total ?? 0})`
);
const json = JSON.stringify(data, null, 2);
if (flags["--output"]) {
await Bun.write(flags["--output"], json);
console.error(`Results saved to: ${flags["--output"]}`);
} else {
console.log(json);
}
}
async function download(flags: Record<string, string>) {
if (!flags["--url"]) {
console.error("Error: --url is required");
process.exit(1);
}
if (!flags["--output"]) {
console.error("Error: --output is required");
process.exit(1);
}
await downloadFile(flags["--url"], flags["--output"]);
}
// ── help ─────────────────────────────────────────────────────────────────
function printHelp() {
console.log(`Pixabay API CLI – search and download royalty-free images & videos
Commands:
search-images Search for images
search-videos Search for videos
download Download a file by URL
Common flags:
--key Pixabay API key (or set PIXABAY_API_KEY env var)
--query / -q Search term (max 100 chars)
--id Retrieve by Pixabay ID
--lang Language code (default: en)
--category Filter by category
--min-width Minimum width in px
--min-height Minimum height in px
--editors-choice Only Editor's Choice results
--safesearch Only results suitable for all ages
--order popular | latest (default: popular)
--page Page number (default: 1)
--per-page Results per page, 5-200 (default: 20)
--output / -o Save JSON to file
Image-specific:
--image-type all | photo | illustration | vector
--orientation all | horizontal | vertical
--colors Comma-separated color filter
Video-specific:
--video-type all | film | animation
Download:
--url URL to download
--output Local file path to save`);
}
// ── main ─────────────────────────────────────────────────────────────────
const rawArgs = process.argv.slice(2);
if (rawArgs.length === 0 || rawArgs[0] === "--help" || rawArgs[0] === "-h") {
printHelp();
process.exit(0);
}
const { command, flags } = parseArgs(rawArgs);
switch (command) {
case "search-images":
await searchImages(flags);
break;
case "search-videos":
await searchVideos(flags);
break;
case "download":
await download(flags);
break;
default:
console.error(`Unknown command: ${command}`);
printHelp();
process.exit(1);
}
```
### references/api_reference.md
```markdown
# API Reference
---
## Pixabay API
### Endpoints
| Endpoint | URL | Description |
|----------|-----|-------------|
| Search Images | `GET https://pixabay.com/api/` | Search royalty-free images |
| Search Videos | `GET https://pixabay.com/api/videos/` | Search royalty-free videos |
### Common Parameters (Images & Videos)
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `key` | string | **required** | API key |
| `q` | string | *(all)* | Search term, max 100 chars |
| `lang` | string | `en` | Language: `cs,da,de,en,es,fr,id,it,hu,nl,no,pl,pt,ro,sk,fi,sv,tr,vi,th,bg,ru,el,ja,ko,zh` |
| `id` | string | – | Retrieve single resource by ID |
| `category` | string | – | `backgrounds,fashion,nature,science,education,feelings,health,people,religion,places,animals,industry,computer,food,sports,transportation,travel,buildings,business,music` |
| `min_width` | int | `0` | Minimum width (px) |
| `min_height` | int | `0` | Minimum height (px) |
| `editors_choice` | bool | `false` | Editor's Choice only |
| `safesearch` | bool | `false` | Safe content only |
| `order` | string | `popular` | `popular` or `latest` |
| `page` | int | `1` | Page number |
| `per_page` | int | `20` | Results per page (5-200) |
### Image-Only Parameters
| Parameter | Type | Default | Values |
|-----------|------|---------|--------|
| `image_type` | string | `all` | `all,photo,illustration,vector` |
| `orientation` | string | `all` | `all,horizontal,vertical` |
| `colors` | string | – | Comma-separated: `grayscale,transparent,red,orange,yellow,green,turquoise,blue,lilac,pink,white,gray,black,brown` |
### Video-Only Parameters
| Parameter | Type | Default | Values |
|-----------|------|---------|--------|
| `video_type` | string | `all` | `all,film,animation` |
### Image Response Fields
| Field | Description |
|-------|-------------|
| `id` | Unique identifier |
| `pageURL` | Source page on Pixabay |
| `type` | `photo`, `illustration`, or `vector` |
| `tags` | Comma-separated tags |
| `previewURL` | Low-res preview (max 150px) |
| `webformatURL` | Medium size (max 640px, valid 24h). Replace `_640` with `_180`,`_340`,`_960` for other sizes |
| `largeImageURL` | Scaled image (max 1280px) |
| `views,downloads,likes,comments` | Engagement metrics |
| `user,user_id,userImageURL` | Contributor info |
### Video Response Fields
| Field | Description |
|-------|-------------|
| `id` | Unique identifier |
| `pageURL` | Source page |
| `type` | `film` or `animation` |
| `tags` | Comma-separated tags |
| `duration` | Duration in seconds |
| `videos` | Object with `large` (3840x2160), `medium` (1920x1080), `small` (1280x720), `tiny` (960x540) renditions. Each has `url,width,height,size,thumbnail` |
| `views,downloads,likes,comments` | Engagement metrics |
| `user,user_id,userImageURL` | Contributor info |
### Rate Limits
- 100 requests / 60 seconds per API key
- Headers: `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`
- Cache results for 24 hours
- HTTP 429 on rate limit exceeded
---
## Freesound API
### Endpoints
| Endpoint | Method | Description | Auth |
|----------|--------|-------------|------|
| `/apiv2/search/` | GET | Search sounds | Token |
| `/apiv2/sounds/<id>/` | GET | Get sound details | Token |
| `/apiv2/sounds/<id>/similar/` | GET | Get similar sounds | Token |
| `/apiv2/sounds/<id>/comments/` | GET | Get sound comments | Token |
| `/apiv2/sounds/<id>/download/` | GET | Download original file | OAuth2 |
### Authentication
#### Token Authentication (Recommended for read-only)
1. Create a Freesound account at https://freesound.org
2. Apply for API credentials at https://freesound.org/apiv2/apply
3. Use the "Client secret/Api key" from your credentials
**Usage:**
```bash
# As GET parameter
curl "https://freesound.org/apiv2/search/?query=piano&token=YOUR_API_KEY"
# As Authorization header
curl -H "Authorization: Token YOUR_API_KEY" "https://freesound.org/apiv2/search/?query=piano"
```
#### OAuth2 Authentication (Required for downloads & write operations)
OAuth2 is required for downloading original files, uploading, rating, etc.
### Search Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `query` | string | – | Search term (supports +/- modifiers, phrases in quotes) |
| `filter` | string | – | Filter results (see filter syntax below) |
| `sort` | string | `score` | Sort order (see sort options below) |
| `fields` | string | `id,name,tags,username,license` | Comma-separated fields to return |
| `page` | int | `1` | Page number |
| `page_size` | int | `15` | Results per page (max 150) |
| `group_by_pack` | bool | `0` | Group results by pack |
| `similar_to` | int/array | – | Find sounds similar to ID or vector |
| `similar_space` | string | – | Similarity space (laion_clap, freesound_classic) |
### Sort Options
| Option | Description |
|--------|-------------|
| `score` | Relevance (default) |
| `duration_desc` / `duration_asc` | By duration |
| `created_desc` / `created_asc` | By upload date |
| `downloads_desc` / `downloads_asc` | By download count |
| `rating_desc` / `rating_asc` | By rating |
### Filter Syntax
```
# Basic filter
filter=tag:piano
# Range filter (numeric)
filter=duration:[0.1 TO 1.0]
filter=avg_rating:[3 TO *]
# Logical operators
filter=type:(wav OR aiff)
filter=description:(piano AND note)
# Geographic filter (distance)
filter={!geofilt sfield=geotag pt=41.3833,2.1833 d=10}
```
### Available Filters
**Basic Metadata:**
- `id`, `name`, `tag`, `description`, `category`, `subcategory`
- `username`, `pack`, `license`, `type` (wav, aiff, ogg, mp3, m4a, flac)
- `channels`, `samplerate`, `bitrate`, `bitdepth`, `filesize`, `duration`
- `created`, `is_geotagged`, `is_remix`, `was_remixed`, `is_explicit`
- `num_downloads`, `avg_rating`, `num_ratings`, `num_comments`
**Audio Descriptors:**
- `bpm`, `bpm_confidence`
- `pitch`, `pitch_min`, `pitch_max`, `pitch_var`
- `note_name`, `note_midi`, `note_confidence`
- `tonality`, `tonality_confidence`
- `loudness`, `dynamic_range`
- `spectral_centroid`, `spectral_entropy`, `spectral_flatness`
- `temporal_centroid`, `log_attack_time`
- `loopable`, `single_event`, `reverbness`
### Sound Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | int | Unique identifier |
| `url` | string | Freesound page URL |
| `name` | string | Sound name |
| `tags` | array | Tag array |
| `description` | string | Description text |
| `category` | string | Top-level category |
| `subcategory` | string | Sub-category |
| `geotag` | string | Latitude/longitude |
| `created` | string | Upload date |
| `license` | string | License type |
| `type` | string | File type (wav, mp3, etc.) |
| `channels` | int | Number of channels |
| `filesize` | int | File size in bytes |
| `bitrate` | int | Bitrate |
| `bitdepth` | int | Bit depth |
| `duration` | float | Duration in seconds |
| `samplerate` | int | Sample rate |
| `username` | string | Uploader username |
| `num_downloads` | int | Download count |
| `avg_rating` | float | Average rating (0-5) |
| `num_ratings` | int | Rating count |
| `num_comments` | int | Comment count |
| `previews` | object | Preview audio URLs |
| `images` | object | Waveform/spectrogram URLs |
### Preview URLs
```json
{
"preview-hq-mp3": "https://... (~128kbps)",
"preview-lq-mp3": "https://... (~64kbps)",
"preview-hq-ogg": "https://... (~192kbps)",
"preview-lq-ogg": "https://... (~80kbps)"
}
```
### Rate Limits & Usage
- API is rate-limited; check response headers for limits
- Cache results when possible
- Attribute Freesound and the sound creator when using sounds
- Respect the license terms of each sound
### License Types
Common licenses on Freesound:
- Creative Commons 0 (Public Domain)
- Creative Commons Attribution
- Creative Commons Attribution Noncommercial
---
## Jamendo API
### Endpoints
| Endpoint | Method | Description |
|----------|--------|-------------|
| `/v3.0/tracks` | GET | Search/get tracks |
| `/v3.0/albums` | GET | Search/get albums |
| `/v3.0/artists` | GET | Search/get artists |
| `/v3.0/artists/tracks` | GET | Get artist's tracks |
| `/v3.0/artists/albums` | GET | Get artist's albums |
### Authentication
1. Create a Jamendo account at https://www.jamendo.com
2. Apply for API credentials at https://devportal.jamendo.com/
3. Use the Client ID in your requests
**Usage:**
```bash
curl "https://api.jamendo.com/v3.0/tracks/?client_id=YOUR_CLIENT_ID&search=rock"
```
### Track Search Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `client_id` | string | **required** | Client ID from devportal |
| `search` | string | – | Free text search |
| `namesearch` | string | – | Fuzzy name search |
| `tags` | string | – | Tag search (AND logic, `+` separated) |
| `fuzzytags` | string | – | Fuzzy tag search (OR logic) |
| `artist_id` | int | – | Filter by artist ID |
| `artist_name` | string | – | Filter by artist name |
| `album_id` | int | – | Filter by album ID |
| `limit` | int | 10 | Results per page (max 200) |
| `offset` | int | 0 | Pagination offset |
| `order` | string | `relevance` | Sort order |
| `include` | string | – | Extra info: `musicinfo`, `stats`, `licenses`, `lyrics` |
### Music Attribute Filters
| Parameter | Values | Description |
|-----------|--------|-------------|
| `vocalinstrumental` | `vocal`, `instrumental` | Vocal or instrumental |
| `acousticelectric` | `acoustic`, `electric` | Acoustic or electric |
| `speed` | `verylow`, `low`, `medium`, `high`, `veryhigh` | Music speed |
| `gender` | `male`, `female` | Vocalist gender |
| `lang` | 2-letter code | Lyrics language |
| `featured` | `true`, `1` | Featured tracks only |
### Date/Duration Filters
| Parameter | Format | Description |
|-----------|--------|-------------|
| `datebetween` | `yyyy-mm-dd_yyyy-mm-dd` | Release date range |
| `durationbetween` | `from_to` | Duration range in seconds |
### Audio Formats
| Format | Quality |
|--------|---------|
| `mp31` | 96kbps MP3 |
| `mp32` | 192kbps VBR MP3 (recommended) |
| `ogg` | OGG Vorbis |
| `flac` | Lossless FLAC |
### Sort Options
Add `_asc` or `_desc` suffix for direction:
- `relevance` (default)
- `popularity_week`, `popularity_month`, `popularity_total`
- `downloads_week`, `downloads_month`, `downloads_total`
- `listens_week`, `listens_month`, `listens_total`
- `name`, `releasedate`, `duration`
### Track Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Track ID |
| `name` | string | Track name |
| `duration` | int | Duration in seconds |
| `artist_id` | string | Artist ID |
| `artist_name` | string | Artist name |
| `album_id` | string | Album ID |
| `album_name` | string | Album name |
| `album_image` | string | Album cover URL |
| `audio` | string | Stream URL |
| `audiodownload` | string | Download URL |
| `audiodownload_allowed` | bool | Whether download is allowed |
| `license_ccurl` | string | Creative Commons license URL |
| `releasedate` | string | Release date (yyyy-mm-dd) |
| `image` | string | Track image URL |
| `shorturl` | string | Short link |
| `shareurl` | string | Share link |
### Music Info (via `include=musicinfo`)
```json
{
"vocalinstrumental": "instrumental",
"lang": "",
"gender": "",
"acousticelectric": "electric",
"speed": "high",
"tags": {
"genres": ["rock", "electronic"],
"instruments": ["guitar", "drums"],
"vartags": ["energetic", "happy"]
}
}
```
### Album Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Album ID |
| `name` | string | Album name |
| `releasedate` | string | Release date |
| `artist_id` | string | Artist ID |
| `artist_name` | string | Artist name |
| `image` | string | Album cover URL |
| `zip` | string | ZIP download URL |
| `zip_allowed` | bool | Whether download is allowed |
### Artist Response Fields
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Artist ID |
| `name` | string | Artist name |
| `website` | string | Artist website |
| `joindate` | string | Join date |
| `image` | string | Artist image URL |
| `shorturl` | string | Short link |
| `shareurl` | string | Share link |
### Rate Limits
- 35,000 requests / month (non-commercial apps)
- Cache results when possible
- Attribution required for Creative Commons licensed content
### License Types
Jamendo tracks are published under Creative Commons licenses:
- CC BY (Attribution)
- CC BY-NC (Attribution-NonCommercial)
- CC BY-ND (Attribution-NoDerivs)
- CC BY-NC-ND (Attribution-NonCommercial-NoDerivs)
- CC BY-SA (Attribution-ShareAlike)
- CC BY-NC-SA (Attribution-NonCommercial-ShareAlike)
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "darknoah",
"slug": "free-resource",
"displayName": "Free Resource",
"latest": {
"version": "0.1.0",
"publishedAt": 1772513964618,
"commit": "https://github.com/openclaw/skills/commit/7ef0ac7bd167ea828cf376c7867e2ae586cbc36e"
},
"history": []
}
```