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.
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/getAudioto 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 theurlfield - 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
/api/getAudioGet full song details including a streamable audio URL and onchain data.
| Parameter | Type | Description |
|---|---|---|
| slug | string | Song URL slug (use this or id) |
| id | UUID | Song UUID (alternative to slug) |
curl https://tortoise.studio/api/getAudio?slug=flux{
"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"
}
}/api/music-metadataList all songs, newest first. Supports pagination.
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number |
| limit | number | Results per page (max 50) |
curl https://tortoise.studio/api/music-metadata?page=1&limit=20{
"data": [{ "id": "...", "title": "...", "artist": "...", ... }],
"pagination": { "page": 1, "limit": 20, "total": 142, ... }
}/api/music-metadata/{id}Get a single song by UUID.
| Parameter | Type | Description |
|---|---|---|
| id | UUID | Song UUID (in path) |
curl https://tortoise.studio/api/music-metadata/550e8400-e29b-41d4-a716-446655440000{
"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"
}/api/songs/detailsBatch fetch songs by IDs.
| Parameter | Type | Description |
|---|---|---|
| ids | string | Comma-separated UUIDs |
curl 'https://tortoise.studio/api/songs/details?ids=id1,id2,id3'{
"songs": [{ "id": "...", "title": "...", ... }]
}/api/songs/trendingTrending songs. Use timeframe=30d for hot songs (ranked by unique collectors in last 30 days). Use sortBy=created_at for newest uploads.
| Parameter | Type | Description |
|---|---|---|
| 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") |
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/songs/trending?timeframe=30d&page=1&limit=20{
"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"
}/api/songs/topAll-time top songs ranked by total collection count.
| Parameter | Type | Description |
|---|---|---|
| media_type | "song" | "pod" | Filter by media type (default "song") |
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/songs/top?page=1&limit=20{
"songs": [{ "id": "...", "title": "...", "collection_count": 128, ... }],
"pagination": { ... },
"timeframe": "all-time"
}/api/songs/collectorsGet the list of collectors for a specific song.
| Parameter | Type | Description |
|---|---|---|
| songId | UUID | Song UUID (required) |
curl https://tortoise.studio/api/songs/collectors?songId=550e8400-e29b-41d4-a716-446655440000{
"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=podCollecting (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
| Parameter | Type | Description |
|---|---|---|
| Chain | Base (chain ID 8453) | |
| Contract (v0.3) | address | 0x54D49546fD0A4FA8F33A9Dd14576D58b40eC5124 |
| Contract (v0.2, legacy) | address | 0xf7465C185Cc511Bd15B570866875c0d48A6B0be0 |
| Standard | ERC-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
- Call
getAudioAPI to get thecontract_song_idandonchainData.price - Determine the contract version from
contract_song_id(see above) - Read
platformFee()from the correct contract - Call
mintSongwithvalue = (price + platformFee) * quantity
// 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) * quantitygetSongDetails(uint256 songId)
→ (string title, address artist, uint256 price,
uint256 maxSupply, uint256 currentSupply, bool exists)
platformFee() → uint256
getArtistSongs(address artist) → uint256[]
uri(uint256 songId) → stringimport { 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
/api/users/collected-songsGet songs a user has collected.
| Parameter | Type | Description |
|---|---|---|
| fid | number | Farcaster FID (required) |
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/users/collected-songs?fid=12345{
"songs": [{ "id": "...", "title": "...", ... }],
"pagination": { ... }
}/api/users/collected-song-idsGet just the IDs of songs a user has collected.
| Parameter | Type | Description |
|---|---|---|
| fid | number | Farcaster FID (required) |
curl https://tortoise.studio/api/users/collected-song-ids?fid=12345{
"songIds": ["id1", "id2", "id3"]
}/api/users/created-songsGet songs uploaded by an artist.
| Parameter | Type | Description |
|---|---|---|
| fid | number | Farcaster FID (required) |
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/users/created-songs?fid=12345{
"songs": [{ "id": "...", "title": "...", ... }],
"pagination": { ... }
}/api/users/created-song-idsGet just the IDs of songs uploaded by an artist.
| Parameter | Type | Description |
|---|---|---|
| fid | number | Farcaster FID (required) |
curl https://tortoise.studio/api/users/created-song-ids?fid=12345{
"songIds": ["id1", "id2", "id3"]
}/api/users/collector-statsTop collectors leaderboard, ranked by number of songs collected.
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/users/collector-stats?page=1&limit=20{
"users": [{ "fid": 12345, "username": "alice", "collection_count": 42 }],
"pagination": { ... }
}/api/users/artist-collection-statsTop artists leaderboard, ranked by total collections received.
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/users/artist-collection-stats?page=1&limit=20{
"users": [{ "fid": 12345, "username": "bob", "total_collections": 256 }],
"pagination": { ... }
}/api/users/by-usernameLook up a user by Farcaster username. Useful for finding someone's FID when you only know their name.
| Parameter | Type | Description |
|---|---|---|
| username | string | Farcaster username (required) |
curl https://tortoise.studio/api/users/by-username?username=alice{
"user": { "fid": 12345, "username": "alice", "display_name": "Alice", "pfp_url": "..." }
}/api/users/statsGet aggregated stats for all users.
curl https://tortoise.studio/api/users/stats{
"stats": [{ "fid": 12345, "songs_collected": 42, "songs_created": 5 }]
}Playlists
/api/playlists/{id}Get a playlist and its tracks. Accepts UUID or slug.
| Parameter | Type | Description |
|---|---|---|
| id | UUID or slug | Playlist identifier (in path) |
curl https://tortoise.studio/api/playlists/my-playlist-slug{
"playlist": {
"id": "...",
"name": "My Playlist",
"slug": "my-playlist-slug",
"owner_fid": 12345,
"songs": [{ "id": "...", "title": "...", ... }]
}
}/api/playlists/by-userGet all playlists created by a user.
| Parameter | Type | Description |
|---|---|---|
| fid | number | Farcaster FID (required) |
curl https://tortoise.studio/api/playlists/by-user?fid=12345{
"playlists": [{ "id": "...", "name": "My Playlist", "slug": "...", ... }]
}/api/playlists/feedPublic playlist feed — recently updated playlists.
| Parameter | Type | Description |
|---|---|---|
| page | number | Page number |
| limit | number | Results per page |
curl https://tortoise.studio/api/playlists/feed?page=1&limit=20{
"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.