# Tortoise Music API Reference Tortoise is an onchain music platform on Base (Farcaster Mini App). Artists upload songs and podcasts, listeners collect them as ERC-1155 NFTs, and everything is streamable through the API. Each song has an IPFS audio file, cover art, and an onchain token you can mint directly from the smart contract. Base URL: https://tortoise.studio No authentication required for public read endpoints. All responses are JSON. Collecting (minting) is done onchain via smart contract — see "Collecting" section. Tortoise works as both a Farcaster Mini App and a standalone web app. Users can sign in with their Farcaster account (giving them an FID) or with a Base Account (wallet-based login on the web). Both types of users can upload, stream, and collect. ## Key Concepts - **FID**: A Farcaster ID — a unique number identifying a user on Farcaster (e.g. fid=12345). Most public API endpoints use FID to look up users and artists. Find someone's FID via GET /api/users/by-username?username={name}. - **Base Account**: A wallet-based identity used on the web app. Base Account users have an internal user ID but may not have an FID. The public API does not currently support looking up Base Account users — user endpoints require an FID. - **Slug**: A URL-friendly song identifier (e.g. "flux"). Use with /api/getAudio to get song details and a streamable audio URL. - **Collecting**: Minting a song's NFT onchain. Supports the artist financially and gives the collector an ERC-1155 token on Base. Requires a wallet and a small amount of ETH. - **IPFS CID**: Content identifier for files on IPFS. Used to construct audio/image URLs via a Pinata gateway. ## Common Tasks - **Stream a song**: GET /api/getAudio?slug={slug} → use the `url` field in the response - **Browse trending music**: GET /api/songs/trending?timeframe=30d - **Browse new uploads**: GET /api/songs/trending?sortBy=created_at - **Find a user's FID**: GET /api/users/by-username?username={name} - **See what a user collected**: GET /api/users/collected-songs?fid={fid} - **See an artist's uploads**: GET /api/users/created-songs?fid={fid} - **Get a playlist**: GET /api/playlists/{id-or-slug} - **Collect (mint) a song**: See "Collecting (Onchain)" section — requires a wallet and ETH on Base ## Pagination Paginated endpoints accept: page (default 1), limit (default 20, max 50). Response includes: pagination: { page, limit, total, totalPages, hasNextPage, hasPrevPage } ## Data Types ### Song { id: string (UUID), title: string, artist: string, artist_fid: number, artist_username: string | null, url_slug: string, contract_song_id: string, collection_count: number, audio_ipfs_cid: string, image_ipfs_cid: string, created_at: string (ISO 8601), media_type: "song" | "pod" } ### AudioDetail (from getAudio) { id: string (UUID), url: string (audio stream URL), title: string, artist: string, imageUrl: string | null, artistFid: number, walletAddress: string, txHash: string, price: string, urlSlug: string, onchainData: { title: string, artist: string, price: string, maxSupply: string, currentSupply: string, exists: boolean, songId: string, frameUrl: string | null, urlSlug: string } } ## Audio URL Construction Audio and image files are stored on IPFS via Pinata. Audio: https://gateway.pinata.cloud/ipfs/{audio_ipfs_cid} Image: https://gateway.pinata.cloud/ipfs/{image_ipfs_cid} --- ## Songs & Audio ### GET /api/getAudio Get full song details including audio stream URL and onchain data. Params: slug (string, required unless id provided) | id (UUID, alternative to slug) Example: GET /api/getAudio?slug=flux Response: AudioDetail object (see Data Types) ### GET /api/music-metadata List all songs, newest first. Supports pagination. Params: page, limit Example: GET /api/music-metadata?page=1&limit=20 Response: { data: Song[], pagination } ### GET /api/music-metadata/{id} Get a single song by UUID. Params: id (UUID, in path) Example: GET /api/music-metadata/550e8400-e29b-41d4-a716-446655440000 Response: Song object ### GET /api/songs/details Batch fetch songs by IDs. Params: ids (comma-separated UUIDs) Example: GET /api/songs/details?ids=id1,id2,id3 Response: { songs: Song[] } ### GET /api/songs/trending Trending songs. Use timeframe=30d for hot songs (sorted by unique collectors in last 30 days). Use sortBy=created_at for newest uploads. Params: timeframe ("30d"), sortBy ("created_at"), media_type ("song"|"pod"), page, limit Example: GET /api/songs/trending?timeframe=30d&page=1&limit=20 Response: { songs: Song[], pagination, timeframe } ### GET /api/songs/top All-time top songs ranked by total collection count. Params: media_type ("song"|"pod"), page, limit Example: GET /api/songs/top?page=1&limit=20 Response: { songs: Song[], pagination, timeframe: "all-time" } ### GET /api/songs/collectors Get the list of collectors for a specific song. Params: songId (UUID, required) Example: GET /api/songs/collectors?songId=550e8400-e29b-41d4-a716-446655440000 Response: { collectors: [...] } --- ## Podcasts All song endpoints support podcasts via the media_type parameter. Set media_type=pod to filter for podcasts only. Example: GET /api/songs/trending?timeframe=30d&media_type=pod --- ## Collecting (Onchain) Songs are collected by minting onchain via the Tortoise smart contract on Base (chain ID 8453). No API call needed — interact with the contract directly. Contract (v0.3, current): 0x54D49546fD0A4FA8F33A9Dd14576D58b40eC5124 Contract (v0.2, legacy): 0xf7465C185Cc511Bd15B570866875c0d48A6B0be0 Standard: ERC-1155 ### Which contract version? The contract_song_id field from the API encodes the version: - Ends with "_0.3" (e.g. "7_0.3") → use v0.3 contract, strip suffix to get songId = 7 - No suffix (e.g. "42") → use v0.2 contract, songId = 42 ### How to collect 1. Call GET /api/getAudio?slug={slug} to get contract_song_id and onchainData.price 2. Determine contract version from contract_song_id (see above) 3. Read platformFee() from the correct contract 4. Call mintSong with value = (price + platformFee) * quantity ### mintSong // v0.3 — takes a recipient address function mintSong(uint256 songId, uint256 quantity, address recipient) payable // v0.2 — mints to msg.sender function mintSong(uint256 songId, uint256 quantity) payable ### Read-only contract functions getSongDetails(uint256 songId) → (title, artist, price, maxSupply, currentSupply, exists) platformFee() → uint256 getArtistSongs(address artist) → uint256[] uri(uint256 songId) → string --- ## Users & Artists ### GET /api/users/collected-songs Get songs a user has collected. Params: fid (number, required), page, limit Example: GET /api/users/collected-songs?fid=12345 Response: { songs: Song[], pagination } ### GET /api/users/collected-song-ids Get just the IDs of songs a user has collected. Params: fid (number, required) Example: GET /api/users/collected-song-ids?fid=12345 Response: { songIds: string[] } ### GET /api/users/created-songs Get songs uploaded by an artist. Params: fid (number, required), page, limit Example: GET /api/users/created-songs?fid=12345 Response: { songs: Song[], pagination } ### GET /api/users/created-song-ids Get just the IDs of songs uploaded by an artist. Params: fid (number, required) Example: GET /api/users/created-song-ids?fid=12345 Response: { songIds: string[] } ### GET /api/users/collector-stats Top collectors leaderboard, ranked by number of songs collected. Params: page, limit Example: GET /api/users/collector-stats?page=1&limit=20 Response: { users: [...], pagination } ### GET /api/users/artist-collection-stats Top artists leaderboard, ranked by total collections received. Params: page, limit Example: GET /api/users/artist-collection-stats?page=1&limit=20 Response: { users: [...], pagination } ### GET /api/users/by-username Look up a user by Farcaster username. Params: username (string, required) Example: GET /api/users/by-username?username=alice Response: { user: { fid, username, display_name, pfp_url, ... } } ### GET /api/users/stats Get aggregated stats for all users. Example: GET /api/users/stats Response: { stats: [...] } --- ## Playlists ### GET /api/playlists/{id} Get a playlist and its tracks. Accepts UUID or slug. Params: id (UUID or slug, in path) Example: GET /api/playlists/my-playlist-slug Response: { playlist: { id, name, slug, owner_fid, songs: Song[], ... } } ### GET /api/playlists/by-user Get all playlists created by a user. Params: fid (number, required) Example: GET /api/playlists/by-user?fid=12345 Response: { playlists: [...] } ### GET /api/playlists/feed Public playlist feed — recently updated playlists. Params: page, limit Example: GET /api/playlists/feed?page=1&limit=20 Response: { playlists: [...], pagination }