{
  "schemaVersion": "1.0",
  "item": {
    "slug": "spoticlaw",
    "name": "SpotiClaw",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/ledzgio/spoticlaw",
    "canonicalUrl": "https://clawhub.ai/ledzgio/spoticlaw",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/spoticlaw",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=spoticlaw",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "README.md",
      "SKILL.md",
      "scripts/__init__.py",
      "scripts/auth.py",
      "scripts/requirements.txt",
      "scripts/spoticlaw.py"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/spoticlaw"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/spoticlaw",
    "agentPageUrl": "https://openagent3.xyz/skills/spoticlaw/agent",
    "manifestUrl": "https://openagent3.xyz/skills/spoticlaw/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/spoticlaw/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Then review README.md for any prerequisites, environment setup, or post-install checks. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Then review README.md for any prerequisites, environment setup, or post-install checks. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "Spoticlaw - Spotify Web API Client",
        "body": "A lightweight Spotify Web API client using direct HTTP requests. No Spotipy dependency."
      },
      {
        "title": "Quick Start",
        "body": "# Install dependencies\npip install requests python-dotenv\n\n# Add to path\nimport sys\nsys.path.insert(0, \"skills/spoticlaw/scripts\")\n\nfrom spoticlaw import player, search, playlists, library\n\n# Search for music\nresults = search().query(\"coldplay\", types=[\"track\"], limit=10)\n\n# Play a track\nplayer().play(uris=[\"spotify:track:...\"])\n\n# Manage playlists\nplaylists().create(\"My Playlist\")\nplaylists().add_items(\"playlist_id\", [\"spotify:track:...\"])\n\n# Save to library\nlibrary().save([\"spotify:track:...\"])\n\nOr run from the scripts directory:\n\ncd skills/spoticlaw/scripts\npython -c \"from spoticlaw import player; player().play(...)\""
      },
      {
        "title": "Required Configuration",
        "body": "Required env vars in agent runtime:\n\nSPOTIFY_CLIENT_ID\nSPOTIFY_CLIENT_SECRET\nSPOTIFY_REDIRECT_URI (recommended: http://127.0.0.1:8888/callback)\n\nRequired file:\n\n.spotify_cache (OAuth token cache)"
      },
      {
        "title": "Authentication",
        "body": "Security Note: Tokens never pass through the AI model. Authentication is done locally, and the token file is copied manually to the agent."
      },
      {
        "title": "Setup",
        "body": "Create a Spotify app at https://developer.spotify.com/dashboard\nGet CLIENT_ID and CLIENT_SECRET\nAdd http://127.0.0.1:8888/callback as Redirect URI\nCreate .env file in your LOCAL machine:\n\nSPOTIFY_CLIENT_ID=your_client_id\nSPOTIFY_CLIENT_SECRET=your_client_secret\nSPOTIFY_REDIRECT_URI=http://127.0.0.1:8888/callback\n\nRun authentication on your LOCAL machine:\n\ncd skills/spoticlaw/scripts\npip install -r requirements.txt\npython auth.py\n\nOpen the displayed URL in your browser, authorize\n\n\nCopy the token file to your agent:\n\n# Linux/Mac - copy to agent's skill folder\ncp .spotify_cache /path/to/agent/skills/spoticlaw/.spotify_cache\n\n# Or if agent is remote, copy via scp, USB, etc.\nscp .spotify_cache user@agent:/path/to/skills/spoticlaw/.spotify_cache\n\nThat's it! No token ever touches the AI. The agent just reads the file."
      },
      {
        "title": "Token Auto-Refresh",
        "body": "The library automatically handles token refresh only if the agent has the same app credentials in .env:\n\nAccess token expires after ~1 hour\nOn first API call after expiry, it uses refresh_token + client credentials to request a new access token\nRequires SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in the agent environment\nIf .spotify_cache exists but .env is missing/mismatched, refresh fails (invalid_client)\nIf you get an error, run python auth.py locally again and copy updated .spotify_cache\n\nFor more on Spotify's OAuth flow, see: https://developer.spotify.com/documentation/web-api/tutorials/code-flow"
      },
      {
        "title": "Required Scopes",
        "body": "The auth.py script requests these scopes:\n\nuser-read-playback-state - Read playback state\nuser-modify-playback-state - Control playback\nplaylist-read-private - Read private playlists\nplaylist-modify-public - Modify public playlists\nplaylist-modify-private - Modify private playlists\nuser-library-read - Read user library\nuser-library-modify - Modify user library\nuser-read-recently-played - Recently played tracks\nuser-top-read - Top tracks/artists\nuser-follow-read - Followed artists"
      },
      {
        "title": "Primitives",
        "body": "Important: Add the module path and install deps first:\n\npip install requests python-dotenv  # Install dependencies\n\n# For agent execution, add to path:\nimport sys\nsys.path.insert(0, \"skills/spoticlaw/scripts\")"
      },
      {
        "title": "User",
        "body": "from spoticlaw import user\n\nuser().me()  # Get current user profile\n\nReturns: {id, display_name, email, country, ...}"
      },
      {
        "title": "Search",
        "body": "from spoticlaw import search\n\n# Search for tracks\nsearch().query(\"song name\", types=[\"track\"], limit=10)\n\n# Search for artists\nsearch().query(\"artist name\", types=[\"artist\"], limit=10)\n\n# Search multiple types\nsearch().query(\"coldplay\", types=[\"track\", \"artist\", \"album\"], limit=10)\n\nParameters:\n\nq: Search query string\ntypes: List of types: track, artist, album, playlist, show, episode, audiobook\nlimit: Max 10 results (Spotify limit)\noffset: Pagination offset"
      },
      {
        "title": "Tracks",
        "body": "from spoticlaw import tracks\n\ntracks().get(\"track_id\")  # Get single track\ntracks().get_multiple([\"id1\", \"id2\"])  # Get multiple tracks\n\nReturns track metadata: {name, artists, album, duration_ms, uri, ...}"
      },
      {
        "title": "Artists",
        "body": "from spoticlaw import artists\n\nartists().get(\"artist_id\")  # Get artist details\nartists().get_albums(\"artist_id\", limit=10)  # Get artist albums\n\nAlbum filters (include_groups):\n\nalbum, single, compilation, appears_on"
      },
      {
        "title": "Albums",
        "body": "from spoticlaw import albums\n\nalbums().get(\"album_id\")  # Get album details\nalbums().get_tracks(\"album_id\")  # Get album tracks"
      },
      {
        "title": "Shows (Podcasts)",
        "body": "from spoticlaw import shows\n\nshows().get(\"show_id\")  # Get show details\nshows().get_episodes(\"show_id\", limit=10)  # Get show episodes"
      },
      {
        "title": "Episodes",
        "body": "from spoticlaw import episodes\n\nepisodes().get(\"episode_id\")  # Get episode details"
      },
      {
        "title": "Playlists",
        "body": "from spoticlaw import playlists, user_playlists\n\n# Get user's playlists\nuser_playlists().get(limit=50)\n\n# Get playlist details\nplaylists().get(\"playlist_id\")\n\n# Get playlist tracks\n# Note: Each item has 'item' key (not 'track'), e.g. item['item']['name']\n# Also can contain episodes (podcasts), not just tracks\nplaylists().get_items(\"playlist_id\", limit=50)\n\n# Create playlist\nplaylists().create(name=\"My Playlist\", description=\"...\", public=False)\n\n# Update playlist\nplaylists().update(\"playlist_id\", name=\"New Name\")\n\n# Add tracks\nplaylists().add_items(\"playlist_id\", [\"spotify:track:...\", \"spotify:track:...\"])\n\n# Remove tracks\nplaylists().remove_items(\"playlist_id\", [\"spotify:track:...\"])\n\n# Delete playlist (unfollow)\nplaylists().delete(\"playlist_id\")"
      },
      {
        "title": "Library",
        "body": "from spoticlaw import library\n\nlibrary().save([\"spotify:track:...\"])  # Save to library\nlibrary().remove([\"spotify:track:...\"])  # Remove from library\nlibrary().check([\"spotify:track:...\"])  # Check if saved, returns [True, False]"
      },
      {
        "title": "Player",
        "body": "from spoticlaw import player\n\n# Get playback state\nplayer().get_playback_state()  # Returns {} if nothing playing\nplayer().get_currently_playing()\n\n# Get devices\nplayer().get_devices()  # Returns list of devices with IDs\n\n# Transfer playback to device\nplayer().transfer(\"device_id\", play=True)\n\n# Playback control\nplayer().play(uris=[\"spotify:track:...\"])  # Start playing\nplayer().play(context_uri=\"spotify:album:...\")  # Play album/playlist\nplayer().pause()\nplayer().next()\nplayer().previous()\nplayer().seek(60000)  # Seek to 1:00 (milliseconds)\nplayer().set_volume(80)  # Set volume 0-100\n\n# Queue\nplayer().add_to_queue(\"spotify:track:...\")\nplayer().get_queue()\n\n# Modes\nplayer().set_shuffle(True)\nplayer().set_repeat(\"off\")  # off, track, context\n\n# Recently played\nplayer().get_recently_played(limit=50)"
      },
      {
        "title": "Personalisation",
        "body": "from spoticlaw import personalisation\n\npersonalisation().get_top(\"tracks\", time_range=\"medium_term\", limit=20)\npersonalisation().get_top(\"artists\", time_range=\"long_term\", limit=20)\n\n# time_range: short_term (4 weeks), medium_term (6 months), long_term (years)"
      },
      {
        "title": "Follow",
        "body": "from spoticlaw import follow\n\nfollow().get_followed(limit=50)  # Get followed artists"
      },
      {
        "title": "Composite Workflows",
        "body": "The primitives can be mixed and matched to create powerful automations. Here are practical examples:"
      },
      {
        "title": "Workflow 1: Play a Specific Song",
        "body": "from spoticlaw import search, player\n\n# 1. Search for the song\nresults = search().query(\"stairway to heaven\", types=[\"track\"], limit=5)\nsong = results[\"tracks\"][\"items\"][0]\nsong_uri = song[\"uri\"]\nprint(f\"Playing: {song['name']} by {song['artists'][0]['name']}\")\n\n# 2. Play it\nplayer().play(uris=[song_uri])"
      },
      {
        "title": "Workflow 2: Create Playlist from Search Results",
        "body": "from spoticlaw import search, playlists\n\n# 1. Search for songs\nresults = search().query(\"led zeppelin\", types=[\"track\"], limit=10)\ntrack_uris = [t[\"uri\"] for t in results[\"tracks\"][\"items\"][:5]]\n\n# 2. Create playlist\npl = playlists().create(\"Led Zeppelin Mix\", public=False)\nplaylist_id = pl[\"id\"]\n\n# 3. Add tracks\nplaylists().add_items(playlist_id, track_uris)\nprint(f\"Created playlist: {pl['name']}\")"
      },
      {
        "title": "Workflow 3: Save Album to Library",
        "body": "from spoticlaw import artists, albums, library\n\n# 1. Find artist\nartist = search().query(\"the weeknd\", types=[\"artist\"], limit=1)[\"artists\"][\"items\"][0]\n\n# 2. Get albums\nalbums_list = artists().get_albums(artist[\"id\"], include_groups=\"album\", limit=5)\n\n# 3. Save first album\nalbum = albums_list[\"items\"][0]\nlibrary().save([album[\"uri\"]])\nprint(f\"Saved album: {album['name']}\")"
      },
      {
        "title": "Workflow 4: Play Podcast Episode",
        "body": "from spoticlaw import search, shows, player\n\n# 1. Find podcast\npodcast = search().query(\"joe rogan\", types=[\"show\"], limit=1)[\"shows\"][\"items\"][0]\nshow_id = podcast[\"id\"]\n\n# 2. Get latest episode\nepisodes = shows().get_episodes(show_id, limit=1)\nepisode = episodes[\"items\"][0]\nepisode_uri = episode[\"uri\"]\n\n# 3. Get device and play\ndevices = player().get_devices()\nif devices.get(\"devices\"):\n    device_id = devices[\"devices\"][0][\"id\"]\n    player().transfer(device_id, play=True)\n    player().play(uris=[episode_uri])\n    print(f\"Playing: {episode['name']}\")"
      },
      {
        "title": "Workflow 5: Transfer Playback and Play",
        "body": "from spoticlaw import player, search\n\n# 1. Get available devices\ndevices = player().get_devices()\nprint(\"Available devices:\", [d[\"name\"] for d in devices.get(\"devices\", [])])\n\n# 2. Transfer to phone\nif devices.get(\"devices\"):\n    device_id = devices[\"devices\"][0][\"id\"]\n    player().transfer(device_id, play=True)\n    \n    # 3. Search and play\n    results = search().query(\"dream on\", types=[\"track\"], limit=1)\n    track_uri = results[\"tracks\"][\"items\"][0][\"uri\"]\n    player().play(uris=[track_uri])"
      },
      {
        "title": "Workflow 6: Get User's Top Artists and Follow One",
        "body": "from spoticlaw import personalisation, search, library\n\n# 1. Get top artists\ntop = personalisation().get_top(\"artists\", limit=10)\nprint(\"Your top artists:\")\nfor i, a in enumerate(top[\"items\"], 1):\n    print(f\"  {i}. {a['name']}\")\n\n# 2. Search for a new artist\nnew_artist = search().query(\"tame impala\", types=[\"artist\"], limit=1)[\"artists\"][\"items\"][0]\nprint(f\"\\nFound: {new_artist['name']}\")\n\n# Note: Follow functionality requires additional scope (not in current scope list)\n# Use Spotify app to follow manually"
      },
      {
        "title": "Workflow 7: Build Queue from Album",
        "body": "from spoticlaw import albums, player\n\n# 1. Get album tracks\nalbum_id = \"4aawyAB9vmqN3uQ7FjRGTy\"  # Example album\ntracks = albums().get_tracks(album_id)\n\n# 2. Add all tracks to queue\nfor track in tracks[\"items\"][:5]:  # First 5 tracks\n    player().add_to_queue(track[\"uri\"])\n\nprint(\"Added 5 tracks to queue\")"
      },
      {
        "title": "Workflow 8: Check Library for Multiple Tracks",
        "body": "from spoticlaw import library, search\n\n# 1. Search for tracks\nresults = search().query(\"classic rock\", types=[\"track\"], limit=20)\ntrack_uris = [t[\"uri\"] for t in results[\"tracks\"][\"items\"]]\n\n# 2. Check which are in library\nsaved = library().check(track_uris)\n\n# 3. Show results\nfor i, (track, is_saved) in enumerate(zip(results[\"tracks\"][\"items\"], saved)):\n    status = \"✓ saved\" if is_saved else \"○ not saved\"\n    print(f\"{i+1}. {track['name']} - {status}\")"
      },
      {
        "title": "Workflow 9: Get Recently Played and Save One",
        "body": "from spoticlaw import player, library\n\n# 1. Get recently played\nrecent = player().get_recently_played(limit=10)\n\nprint(\"Recently played:\")\nfor i, item in enumerate(recent[\"items\"], 1):\n    track = item[\"track\"]\n    print(f\"  {i}. {track['name']} - {track['artists'][0]['name']}\")\n\n# 2. Save the first one\nif recent[\"items\"]:\n    track_uri = recent[\"items\"][0][\"track\"][\"uri\"]\n    library().save([track_uri])\n    print(f\"\\nSaved: {recent['items'][0]['track']['name']}\")"
      },
      {
        "title": "Workflow 10: Play from Playlist",
        "body": "from spoticlaw import playlists, player\n\n# 1. Get user's playlists\nmy_playlists = user_playlists().get(limit=10)\n\nprint(\"Your playlists:\")\nfor p in my_playlists[\"items\"]:\n    print(f\"  - {p['name']} ({p['tracks']['total']} tracks)\")\n\n# 2. Pick first playlist and play\nif my_playlists[\"items\"]:\n    playlist_id = my_playlists[\"items\"][0][\"id\"]\n    # Get device first\n    devices = player().get_devices()\n    if devices.get(\"devices\"):\n        player().transfer(devices[\"devices\"][0][\"id\"], play=True)\n        \n        # Play playlist\n        player().play(context_uri=f\"spotify:playlist:{playlist_id}\")\n        print(f\"Playing: {my_playlists['items'][0]['name']}\")"
      },
      {
        "title": "Error Handling",
        "body": "from spoticlaw import player, SpotifyException\n\ntry:\n    player().play(uris=[\"spotify:track:...\"])\nexcept SpotifyException as e:\n    if \"NO_ACTIVE_DEVICE\" in str(e):\n        print(\"No device found. Open Spotify and try again.\")\n    elif \"Invalid token\" in str(e):\n        print(\"Token expired. Re-authenticate: python auth.py\")\n    else:\n        print(f\"Error: {e}\")"
      },
      {
        "title": "Common Issues",
        "body": "ErrorSolutionNo token. Re-authenticate.Run python auth.pyThe access token expiredShould auto-refresh. If not, run python auth.pyInsufficient client scopeRe-auth with more scopesNO_ACTIVE_DEVICEOpen Spotify app, then retryInvalid limitUse max 10 for search, 50 for playlistsResource not foundInvalid ID or item unavailable"
      },
      {
        "title": "API Limits",
        "body": "Search: max 10 results\nPlaylist items: max 50\nPagination: Use offset parameter\nPlayer: Requires active Spotify session"
      },
      {
        "title": "Files",
        "body": "scripts/spoticlaw.py - Main API client\nscripts/auth.py - Authentication helper\nscripts/requirements.txt - Dependencies"
      },
      {
        "title": "More Info",
        "body": "For setup, troubleshooting, and contributions:\nhttps://github.com/your-org/spoticlaw"
      }
    ],
    "body": "Spoticlaw - Spotify Web API Client\n\nA lightweight Spotify Web API client using direct HTTP requests. No Spotipy dependency.\n\nQuick Start\n# Install dependencies\npip install requests python-dotenv\n\n# Add to path\nimport sys\nsys.path.insert(0, \"skills/spoticlaw/scripts\")\n\nfrom spoticlaw import player, search, playlists, library\n\n# Search for music\nresults = search().query(\"coldplay\", types=[\"track\"], limit=10)\n\n# Play a track\nplayer().play(uris=[\"spotify:track:...\"])\n\n# Manage playlists\nplaylists().create(\"My Playlist\")\nplaylists().add_items(\"playlist_id\", [\"spotify:track:...\"])\n\n# Save to library\nlibrary().save([\"spotify:track:...\"])\n\n\nOr run from the scripts directory:\n\ncd skills/spoticlaw/scripts\npython -c \"from spoticlaw import player; player().play(...)\"\n\nRequired Configuration\n\nRequired env vars in agent runtime:\n\nSPOTIFY_CLIENT_ID\nSPOTIFY_CLIENT_SECRET\nSPOTIFY_REDIRECT_URI (recommended: http://127.0.0.1:8888/callback)\n\nRequired file:\n\n.spotify_cache (OAuth token cache)\nAuthentication\n\nSecurity Note: Tokens never pass through the AI model. Authentication is done locally, and the token file is copied manually to the agent.\n\nSetup\nCreate a Spotify app at https://developer.spotify.com/dashboard\nGet CLIENT_ID and CLIENT_SECRET\nAdd http://127.0.0.1:8888/callback as Redirect URI\nCreate .env file in your LOCAL machine:\nSPOTIFY_CLIENT_ID=your_client_id\nSPOTIFY_CLIENT_SECRET=your_client_secret\nSPOTIFY_REDIRECT_URI=http://127.0.0.1:8888/callback\n\nRun authentication on your LOCAL machine:\ncd skills/spoticlaw/scripts\npip install -r requirements.txt\npython auth.py\n\n\nOpen the displayed URL in your browser, authorize\n\nCopy the token file to your agent:\n\n# Linux/Mac - copy to agent's skill folder\ncp .spotify_cache /path/to/agent/skills/spoticlaw/.spotify_cache\n\n# Or if agent is remote, copy via scp, USB, etc.\nscp .spotify_cache user@agent:/path/to/skills/spoticlaw/.spotify_cache\n\n\nThat's it! No token ever touches the AI. The agent just reads the file.\n\nToken Auto-Refresh\n\nThe library automatically handles token refresh only if the agent has the same app credentials in .env:\n\nAccess token expires after ~1 hour\nOn first API call after expiry, it uses refresh_token + client credentials to request a new access token\nRequires SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in the agent environment\nIf .spotify_cache exists but .env is missing/mismatched, refresh fails (invalid_client)\nIf you get an error, run python auth.py locally again and copy updated .spotify_cache\n\nFor more on Spotify's OAuth flow, see: https://developer.spotify.com/documentation/web-api/tutorials/code-flow\n\nRequired Scopes\n\nThe auth.py script requests these scopes:\n\nuser-read-playback-state - Read playback state\nuser-modify-playback-state - Control playback\nplaylist-read-private - Read private playlists\nplaylist-modify-public - Modify public playlists\nplaylist-modify-private - Modify private playlists\nuser-library-read - Read user library\nuser-library-modify - Modify user library\nuser-read-recently-played - Recently played tracks\nuser-top-read - Top tracks/artists\nuser-follow-read - Followed artists\nPrimitives\n\nImportant: Add the module path and install deps first:\n\npip install requests python-dotenv  # Install dependencies\n\n# For agent execution, add to path:\nimport sys\nsys.path.insert(0, \"skills/spoticlaw/scripts\")\n\nUser\nfrom spoticlaw import user\n\nuser().me()  # Get current user profile\n\n\nReturns: {id, display_name, email, country, ...}\n\nSearch\nfrom spoticlaw import search\n\n# Search for tracks\nsearch().query(\"song name\", types=[\"track\"], limit=10)\n\n# Search for artists\nsearch().query(\"artist name\", types=[\"artist\"], limit=10)\n\n# Search multiple types\nsearch().query(\"coldplay\", types=[\"track\", \"artist\", \"album\"], limit=10)\n\n\nParameters:\n\nq: Search query string\ntypes: List of types: track, artist, album, playlist, show, episode, audiobook\nlimit: Max 10 results (Spotify limit)\noffset: Pagination offset\nTracks\nfrom spoticlaw import tracks\n\ntracks().get(\"track_id\")  # Get single track\ntracks().get_multiple([\"id1\", \"id2\"])  # Get multiple tracks\n\n\nReturns track metadata: {name, artists, album, duration_ms, uri, ...}\n\nArtists\nfrom spoticlaw import artists\n\nartists().get(\"artist_id\")  # Get artist details\nartists().get_albums(\"artist_id\", limit=10)  # Get artist albums\n\n\nAlbum filters (include_groups):\n\nalbum, single, compilation, appears_on\nAlbums\nfrom spoticlaw import albums\n\nalbums().get(\"album_id\")  # Get album details\nalbums().get_tracks(\"album_id\")  # Get album tracks\n\nShows (Podcasts)\nfrom spoticlaw import shows\n\nshows().get(\"show_id\")  # Get show details\nshows().get_episodes(\"show_id\", limit=10)  # Get show episodes\n\nEpisodes\nfrom spoticlaw import episodes\n\nepisodes().get(\"episode_id\")  # Get episode details\n\nPlaylists\nfrom spoticlaw import playlists, user_playlists\n\n# Get user's playlists\nuser_playlists().get(limit=50)\n\n# Get playlist details\nplaylists().get(\"playlist_id\")\n\n# Get playlist tracks\n# Note: Each item has 'item' key (not 'track'), e.g. item['item']['name']\n# Also can contain episodes (podcasts), not just tracks\nplaylists().get_items(\"playlist_id\", limit=50)\n\n# Create playlist\nplaylists().create(name=\"My Playlist\", description=\"...\", public=False)\n\n# Update playlist\nplaylists().update(\"playlist_id\", name=\"New Name\")\n\n# Add tracks\nplaylists().add_items(\"playlist_id\", [\"spotify:track:...\", \"spotify:track:...\"])\n\n# Remove tracks\nplaylists().remove_items(\"playlist_id\", [\"spotify:track:...\"])\n\n# Delete playlist (unfollow)\nplaylists().delete(\"playlist_id\")\n\nLibrary\nfrom spoticlaw import library\n\nlibrary().save([\"spotify:track:...\"])  # Save to library\nlibrary().remove([\"spotify:track:...\"])  # Remove from library\nlibrary().check([\"spotify:track:...\"])  # Check if saved, returns [True, False]\n\nPlayer\nfrom spoticlaw import player\n\n# Get playback state\nplayer().get_playback_state()  # Returns {} if nothing playing\nplayer().get_currently_playing()\n\n# Get devices\nplayer().get_devices()  # Returns list of devices with IDs\n\n# Transfer playback to device\nplayer().transfer(\"device_id\", play=True)\n\n# Playback control\nplayer().play(uris=[\"spotify:track:...\"])  # Start playing\nplayer().play(context_uri=\"spotify:album:...\")  # Play album/playlist\nplayer().pause()\nplayer().next()\nplayer().previous()\nplayer().seek(60000)  # Seek to 1:00 (milliseconds)\nplayer().set_volume(80)  # Set volume 0-100\n\n# Queue\nplayer().add_to_queue(\"spotify:track:...\")\nplayer().get_queue()\n\n# Modes\nplayer().set_shuffle(True)\nplayer().set_repeat(\"off\")  # off, track, context\n\n# Recently played\nplayer().get_recently_played(limit=50)\n\nPersonalisation\nfrom spoticlaw import personalisation\n\npersonalisation().get_top(\"tracks\", time_range=\"medium_term\", limit=20)\npersonalisation().get_top(\"artists\", time_range=\"long_term\", limit=20)\n\n# time_range: short_term (4 weeks), medium_term (6 months), long_term (years)\n\nFollow\nfrom spoticlaw import follow\n\nfollow().get_followed(limit=50)  # Get followed artists\n\nComposite Workflows\n\nThe primitives can be mixed and matched to create powerful automations. Here are practical examples:\n\nWorkflow 1: Play a Specific Song\nfrom spoticlaw import search, player\n\n# 1. Search for the song\nresults = search().query(\"stairway to heaven\", types=[\"track\"], limit=5)\nsong = results[\"tracks\"][\"items\"][0]\nsong_uri = song[\"uri\"]\nprint(f\"Playing: {song['name']} by {song['artists'][0]['name']}\")\n\n# 2. Play it\nplayer().play(uris=[song_uri])\n\nWorkflow 2: Create Playlist from Search Results\nfrom spoticlaw import search, playlists\n\n# 1. Search for songs\nresults = search().query(\"led zeppelin\", types=[\"track\"], limit=10)\ntrack_uris = [t[\"uri\"] for t in results[\"tracks\"][\"items\"][:5]]\n\n# 2. Create playlist\npl = playlists().create(\"Led Zeppelin Mix\", public=False)\nplaylist_id = pl[\"id\"]\n\n# 3. Add tracks\nplaylists().add_items(playlist_id, track_uris)\nprint(f\"Created playlist: {pl['name']}\")\n\nWorkflow 3: Save Album to Library\nfrom spoticlaw import artists, albums, library\n\n# 1. Find artist\nartist = search().query(\"the weeknd\", types=[\"artist\"], limit=1)[\"artists\"][\"items\"][0]\n\n# 2. Get albums\nalbums_list = artists().get_albums(artist[\"id\"], include_groups=\"album\", limit=5)\n\n# 3. Save first album\nalbum = albums_list[\"items\"][0]\nlibrary().save([album[\"uri\"]])\nprint(f\"Saved album: {album['name']}\")\n\nWorkflow 4: Play Podcast Episode\nfrom spoticlaw import search, shows, player\n\n# 1. Find podcast\npodcast = search().query(\"joe rogan\", types=[\"show\"], limit=1)[\"shows\"][\"items\"][0]\nshow_id = podcast[\"id\"]\n\n# 2. Get latest episode\nepisodes = shows().get_episodes(show_id, limit=1)\nepisode = episodes[\"items\"][0]\nepisode_uri = episode[\"uri\"]\n\n# 3. Get device and play\ndevices = player().get_devices()\nif devices.get(\"devices\"):\n    device_id = devices[\"devices\"][0][\"id\"]\n    player().transfer(device_id, play=True)\n    player().play(uris=[episode_uri])\n    print(f\"Playing: {episode['name']}\")\n\nWorkflow 5: Transfer Playback and Play\nfrom spoticlaw import player, search\n\n# 1. Get available devices\ndevices = player().get_devices()\nprint(\"Available devices:\", [d[\"name\"] for d in devices.get(\"devices\", [])])\n\n# 2. Transfer to phone\nif devices.get(\"devices\"):\n    device_id = devices[\"devices\"][0][\"id\"]\n    player().transfer(device_id, play=True)\n    \n    # 3. Search and play\n    results = search().query(\"dream on\", types=[\"track\"], limit=1)\n    track_uri = results[\"tracks\"][\"items\"][0][\"uri\"]\n    player().play(uris=[track_uri])\n\nWorkflow 6: Get User's Top Artists and Follow One\nfrom spoticlaw import personalisation, search, library\n\n# 1. Get top artists\ntop = personalisation().get_top(\"artists\", limit=10)\nprint(\"Your top artists:\")\nfor i, a in enumerate(top[\"items\"], 1):\n    print(f\"  {i}. {a['name']}\")\n\n# 2. Search for a new artist\nnew_artist = search().query(\"tame impala\", types=[\"artist\"], limit=1)[\"artists\"][\"items\"][0]\nprint(f\"\\nFound: {new_artist['name']}\")\n\n# Note: Follow functionality requires additional scope (not in current scope list)\n# Use Spotify app to follow manually\n\nWorkflow 7: Build Queue from Album\nfrom spoticlaw import albums, player\n\n# 1. Get album tracks\nalbum_id = \"4aawyAB9vmqN3uQ7FjRGTy\"  # Example album\ntracks = albums().get_tracks(album_id)\n\n# 2. Add all tracks to queue\nfor track in tracks[\"items\"][:5]:  # First 5 tracks\n    player().add_to_queue(track[\"uri\"])\n\nprint(\"Added 5 tracks to queue\")\n\nWorkflow 8: Check Library for Multiple Tracks\nfrom spoticlaw import library, search\n\n# 1. Search for tracks\nresults = search().query(\"classic rock\", types=[\"track\"], limit=20)\ntrack_uris = [t[\"uri\"] for t in results[\"tracks\"][\"items\"]]\n\n# 2. Check which are in library\nsaved = library().check(track_uris)\n\n# 3. Show results\nfor i, (track, is_saved) in enumerate(zip(results[\"tracks\"][\"items\"], saved)):\n    status = \"✓ saved\" if is_saved else \"○ not saved\"\n    print(f\"{i+1}. {track['name']} - {status}\")\n\nWorkflow 9: Get Recently Played and Save One\nfrom spoticlaw import player, library\n\n# 1. Get recently played\nrecent = player().get_recently_played(limit=10)\n\nprint(\"Recently played:\")\nfor i, item in enumerate(recent[\"items\"], 1):\n    track = item[\"track\"]\n    print(f\"  {i}. {track['name']} - {track['artists'][0]['name']}\")\n\n# 2. Save the first one\nif recent[\"items\"]:\n    track_uri = recent[\"items\"][0][\"track\"][\"uri\"]\n    library().save([track_uri])\n    print(f\"\\nSaved: {recent['items'][0]['track']['name']}\")\n\nWorkflow 10: Play from Playlist\nfrom spoticlaw import playlists, player\n\n# 1. Get user's playlists\nmy_playlists = user_playlists().get(limit=10)\n\nprint(\"Your playlists:\")\nfor p in my_playlists[\"items\"]:\n    print(f\"  - {p['name']} ({p['tracks']['total']} tracks)\")\n\n# 2. Pick first playlist and play\nif my_playlists[\"items\"]:\n    playlist_id = my_playlists[\"items\"][0][\"id\"]\n    # Get device first\n    devices = player().get_devices()\n    if devices.get(\"devices\"):\n        player().transfer(devices[\"devices\"][0][\"id\"], play=True)\n        \n        # Play playlist\n        player().play(context_uri=f\"spotify:playlist:{playlist_id}\")\n        print(f\"Playing: {my_playlists['items'][0]['name']}\")\n\nError Handling\nfrom spoticlaw import player, SpotifyException\n\ntry:\n    player().play(uris=[\"spotify:track:...\"])\nexcept SpotifyException as e:\n    if \"NO_ACTIVE_DEVICE\" in str(e):\n        print(\"No device found. Open Spotify and try again.\")\n    elif \"Invalid token\" in str(e):\n        print(\"Token expired. Re-authenticate: python auth.py\")\n    else:\n        print(f\"Error: {e}\")\n\nCommon Issues\nError\tSolution\nNo token. Re-authenticate.\tRun python auth.py\nThe access token expired\tShould auto-refresh. If not, run python auth.py\nInsufficient client scope\tRe-auth with more scopes\nNO_ACTIVE_DEVICE\tOpen Spotify app, then retry\nInvalid limit\tUse max 10 for search, 50 for playlists\nResource not found\tInvalid ID or item unavailable\nAPI Limits\nSearch: max 10 results\nPlaylist items: max 50\nPagination: Use offset parameter\nPlayer: Requires active Spotify session\nFiles\nscripts/spoticlaw.py - Main API client\nscripts/auth.py - Authentication helper\nscripts/requirements.txt - Dependencies\nMore Info\n\nFor setup, troubleshooting, and contributions: https://github.com/your-org/spoticlaw"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/ledzgio/spoticlaw",
    "publisherUrl": "https://clawhub.ai/ledzgio/spoticlaw",
    "owner": "ledzgio",
    "version": "1.0.7",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/spoticlaw",
    "downloadUrl": "https://openagent3.xyz/downloads/spoticlaw",
    "agentUrl": "https://openagent3.xyz/skills/spoticlaw/agent",
    "manifestUrl": "https://openagent3.xyz/skills/spoticlaw/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/spoticlaw/agent.md"
  }
}