Tortoise API Reference

Public endpoints for streaming, searching, and discovering music on Tortoise.

Building with Tortoise?

Copy the full API reference and paste it into your AI coding agent for instant context.

Download llm.txt

Overview

Tortoise is an onchain music platform on Base. 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.

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 music.

Use this API to browse the catalog, stream audio, look up artists and collectors, and build your own players or bots. Collecting (minting) happens onchain — see the Collecting section.

Key Concepts

  • FID — A Farcaster ID, a unique number identifying a user on the Farcaster social network (e.g. fid=12345). Most public API endpoints use FID to look up users and artists. You can find someone's FID by searching for their username with /api/users/by-username.
  • 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. This 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 and image URLs via a Pinata gateway.

Common Tasks

  • Stream a song GET /api/getAudio?slug={slug} → use the url field
  • Browse trending GET /api/songs/trending?timeframe=30d
  • New uploads GET /api/songs/trending?sortBy=created_at
  • Find a user's FID GET /api/users/by-username?username={name}
  • User's collection GET /api/users/collected-songs?fid={fid}
  • Artist's uploads GET /api/users/created-songs?fid={fid}
  • Collect a song — Onchain via smart contract, see Collecting

Base URL: https://tortoise.studio

No authentication is required for the public endpoints listed below. All responses are JSON.

Paginated endpoints accept page (default 1) and limit (default 20, max 50) query parameters. Responses include a pagination object:

{
  "page": 1,
  "limit": 20,
  "total": 142,
  "totalPages": 8,
  "hasNextPage": true,
  "hasPrevPage": false
}

Songs & Audio

GET/api/getAudio

Get full song details including a streamable audio URL and onchain data.

ParameterTypeDescription
slugstringSong URL slug (use this or id)
idUUIDSong UUID (alternative to slug)
Example
curl https://tortoise.studio/api/getAudio?slug=flux
Response
{
  "id": "550e8400-...",
  "url": "https://gateway.pinata.cloud/ipfs/...",
  "title": "Flux",
  "artist": "Artist Name",
  "imageUrl": "https://gateway.pinata.cloud/ipfs/...",
  "artistFid": 12345,
  "walletAddress": "0x...",
  "price": "0.0007",
  "urlSlug": "flux",
  "onchainData": {
    "title": "Flux",
    "artist": "0x...",
    "price": "700000000000000",
    "maxSupply": "10000",
    "currentSupply": "42",
    "exists": true,
    "songId": "7"
  }
}
GET/api/music-metadata

List all songs, newest first. Supports pagination.

ParameterTypeDescription
pagenumberPage number
limitnumberResults per page (max 50)
Example
curl https://tortoise.studio/api/music-metadata?page=1&limit=20
Response
{
  "data": [{ "id": "...", "title": "...", "artist": "...", ... }],
  "pagination": { "page": 1, "limit": 20, "total": 142, ... }
}
GET/api/music-metadata/{id}

Get a single song by UUID.

ParameterTypeDescription
idUUIDSong UUID (in path)
Example
curl https://tortoise.studio/api/music-metadata/550e8400-e29b-41d4-a716-446655440000
Response
{
  "id": "550e8400-...",
  "title": "Flux",
  "artist": "Artist Name",
  "url_slug": "flux",
  "audio_ipfs_cid": "Qm...",
  "image_ipfs_cid": "Qm...",
  "created_at": "2025-01-15T12:00:00Z",
  "media_type": "song"
}
GET/api/songs/details

Batch fetch songs by IDs.

ParameterTypeDescription
idsstringComma-separated UUIDs
Example
curl 'https://tortoise.studio/api/songs/details?ids=id1,id2,id3'
Response
{
  "songs": [{ "id": "...", "title": "...", ... }]
}
GET/api/songs/trending

Trending songs. Use timeframe=30d for hot songs (ranked by unique collectors in last 30 days). Use sortBy=created_at for newest uploads.

ParameterTypeDescription
timeframe"30d"Hot songs in last 30 days
sortBy"created_at"Sort by newest uploads
media_type"song" | "pod"Filter by media type (default "song")
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/songs/trending?timeframe=30d&page=1&limit=20
Response
{
  "songs": [{
    "id": "...",
    "title": "Flux",
    "artist": "Artist Name",
    "artist_fid": 12345,
    "url_slug": "flux",
    "collection_count": 42,
    "recent_collection_count": 15,
    "audio_ipfs_cid": "Qm...",
    "image_ipfs_cid": "Qm..."
  }],
  "pagination": { ... },
  "timeframe": "30d"
}
GET/api/songs/top

All-time top songs ranked by total collection count.

ParameterTypeDescription
media_type"song" | "pod"Filter by media type (default "song")
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/songs/top?page=1&limit=20
Response
{
  "songs": [{ "id": "...", "title": "...", "collection_count": 128, ... }],
  "pagination": { ... },
  "timeframe": "all-time"
}
GET/api/songs/collectors

Get the list of collectors for a specific song.

ParameterTypeDescription
songIdUUIDSong UUID (required)
Example
curl https://tortoise.studio/api/songs/collectors?songId=550e8400-e29b-41d4-a716-446655440000
Response
{
  "collectors": [
    { "fid": 12345, "username": "alice", "collected_at": "2025-01-15T12:00:00Z" }
  ]
}

Podcasts

All song endpoints support podcasts via the media_type parameter. Set media_type=pod to filter for podcasts only.

curl https://tortoise.studio/api/songs/trending?timeframe=30d&media_type=pod

Collecting (Onchain)

Songs on Tortoise are collected by minting onchain via the Tortoise smart contract on Base (chain ID 8453). You need a wallet with ETH on Base. No Tortoise API call is needed for the mint itself — you interact with the contract directly.

Contract Details

ParameterTypeDescription
ChainBase (chain ID 8453)
Contract (v0.3)address0x54D49546fD0A4FA8F33A9Dd14576D58b40eC5124
Contract (v0.2, legacy)address0xf7465C185Cc511Bd15B570866875c0d48A6B0be0
StandardERC-1155

Which contract version?

The contract_song_id field from the API tells you which contract to use. If it ends with _0.3 (e.g. "7_0.3"), use the v0.3 contract and strip the suffix to get the numeric song ID. Otherwise use the v0.2 contract as-is.

// Determine contract version from contract_song_id
const contractSongId = song.contract_song_id; // e.g. "7_0.3" or "42"

if (contractSongId.includes("_0.3")) {
  // → v0.3 contract: 0x54D49546fD0A4FA8F33A9Dd14576D58b40eC5124
  // → songId = 7  (strip the "_0.3" suffix)
} else {
  // → v0.2 contract: 0xf7465C185Cc511Bd15B570866875c0d48A6B0be0
  // → songId = 42  (use as-is)
}

How to collect a song

  1. Call getAudio API to get the contract_song_id and onchainData.price
  2. Determine the 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

// value = (price + platformFee) * quantity
Read-only helpers
getSongDetails(uint256 songId)
  → (string title, address artist, uint256 price,
     uint256 maxSupply, uint256 currentSupply, bool exists)

platformFee() → uint256

getArtistSongs(address artist) → uint256[]

uri(uint256 songId) → string
Example (viem)
import { parseAbi } from "viem";

const CONTRACTS = {
  "0.3": "0x54D49546fD0A4FA8F33A9Dd14576D58b40eC5124",
  "0.2": "0xf7465C185Cc511Bd15B570866875c0d48A6B0be0",
};

// 1. Get song details from API
const res = await fetch("https://tortoise.studio/api/getAudio?slug=flux");
const data = await res.json();
const contractSongId = data.onchainData.songId; // e.g. "7_0.3" or "42"

// 2. Determine contract version and parse song ID
const isV03 = contractSongId.includes("_0.3");
const contract = isV03 ? CONTRACTS["0.3"] : CONTRACTS["0.2"];
const songId = BigInt(isV03 ? contractSongId.replace("_0.3", "") : contractSongId);

// 3. Read platform fee
const platformFee = await publicClient.readContract({
  address: contract,
  abi: parseAbi(["function platformFee() view returns (uint256)"]),
  functionName: "platformFee",
});

// 4. Mint
const price = BigInt(data.onchainData.price);
const quantity = 1n;

await walletClient.writeContract({
  address: contract,
  abi: parseAbi([
    isV03
      ? "function mintSong(uint256 songId, uint256 quantity, address recipient) payable"
      : "function mintSong(uint256 songId, uint256 quantity) payable",
  ]),
  functionName: "mintSong",
  args: isV03 ? [songId, quantity, recipientAddress] : [songId, quantity],
  value: (price + platformFee) * quantity,
});

Users & Artists

GET/api/users/collected-songs

Get songs a user has collected.

ParameterTypeDescription
fidnumberFarcaster FID (required)
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/users/collected-songs?fid=12345
Response
{
  "songs": [{ "id": "...", "title": "...", ... }],
  "pagination": { ... }
}
GET/api/users/collected-song-ids

Get just the IDs of songs a user has collected.

ParameterTypeDescription
fidnumberFarcaster FID (required)
Example
curl https://tortoise.studio/api/users/collected-song-ids?fid=12345
Response
{
  "songIds": ["id1", "id2", "id3"]
}
GET/api/users/created-songs

Get songs uploaded by an artist.

ParameterTypeDescription
fidnumberFarcaster FID (required)
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/users/created-songs?fid=12345
Response
{
  "songs": [{ "id": "...", "title": "...", ... }],
  "pagination": { ... }
}
GET/api/users/created-song-ids

Get just the IDs of songs uploaded by an artist.

ParameterTypeDescription
fidnumberFarcaster FID (required)
Example
curl https://tortoise.studio/api/users/created-song-ids?fid=12345
Response
{
  "songIds": ["id1", "id2", "id3"]
}
GET/api/users/collector-stats

Top collectors leaderboard, ranked by number of songs collected.

ParameterTypeDescription
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/users/collector-stats?page=1&limit=20
Response
{
  "users": [{ "fid": 12345, "username": "alice", "collection_count": 42 }],
  "pagination": { ... }
}
GET/api/users/artist-collection-stats

Top artists leaderboard, ranked by total collections received.

ParameterTypeDescription
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/users/artist-collection-stats?page=1&limit=20
Response
{
  "users": [{ "fid": 12345, "username": "bob", "total_collections": 256 }],
  "pagination": { ... }
}
GET/api/users/by-username

Look up a user by Farcaster username. Useful for finding someone's FID when you only know their name.

ParameterTypeDescription
usernamestringFarcaster username (required)
Example
curl https://tortoise.studio/api/users/by-username?username=alice
Response
{
  "user": { "fid": 12345, "username": "alice", "display_name": "Alice", "pfp_url": "..." }
}
GET/api/users/stats

Get aggregated stats for all users.

Example
curl https://tortoise.studio/api/users/stats
Response
{
  "stats": [{ "fid": 12345, "songs_collected": 42, "songs_created": 5 }]
}

Playlists

GET/api/playlists/{id}

Get a playlist and its tracks. Accepts UUID or slug.

ParameterTypeDescription
idUUID or slugPlaylist identifier (in path)
Example
curl https://tortoise.studio/api/playlists/my-playlist-slug
Response
{
  "playlist": {
    "id": "...",
    "name": "My Playlist",
    "slug": "my-playlist-slug",
    "owner_fid": 12345,
    "songs": [{ "id": "...", "title": "...", ... }]
  }
}
GET/api/playlists/by-user

Get all playlists created by a user.

ParameterTypeDescription
fidnumberFarcaster FID (required)
Example
curl https://tortoise.studio/api/playlists/by-user?fid=12345
Response
{
  "playlists": [{ "id": "...", "name": "My Playlist", "slug": "...", ... }]
}
GET/api/playlists/feed

Public playlist feed — recently updated playlists.

ParameterTypeDescription
pagenumberPage number
limitnumberResults per page
Example
curl https://tortoise.studio/api/playlists/feed?page=1&limit=20
Response
{
  "playlists": [{ "id": "...", "name": "...", ... }],
  "pagination": { ... }
}

Data Types

Song

The standard song object returned by most endpoints.

{
  "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""
}

Audio URL Construction

Audio and image files are stored on IPFS via Pinata. Construct URLs using the CID fields:

Audio: https://gateway.pinata.cloud/ipfs/{audio_ipfs_cid}
Image: https://gateway.pinata.cloud/ipfs/{image_ipfs_cid}

Pagination

{
  "page": "number",
  "limit": "number",
  "total": "number",
  "totalPages": "number",
  "hasNextPage": "boolean",
  "hasPrevPage": "boolean"
}

Questions? Reach out on Farcaster.