Back to skills
SkillHub ClubShip Full StackFull Stack

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.

Stars
3,077
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
B70.0

Install command

npx @skill-hub/cli install openclaw-skills-free-resource

Repository

openclaw/skills

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 repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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": []
}

```