🚀 Getting Started
SnapieAudioPlayer provides a REST API for uploading, storing, and playing audio files on the decentralized IPFS network. All audio files are stored permanently on IPFS and metadata is indexed in our MongoDB database for fast retrieval.
https://audio.3speak.tv
Quick Start
- Get an API key (contact 3speak team)
- Upload audio via REST API
- Embed player using returned permlink or CID
- Track plays and engagement
🔐 Authentication
API keys are required for uploading audio. Include your API key in the request header:
X-API-Key: sk_your_api_key_here
📺 Embedding Player
Using iFrame
<!-- Embed by permlink -->
<iframe
src="https://audio.3speak.tv/play?a=abc123xyz"
width="100%"
height="120"
frameborder="0"
allow="autoplay">
</iframe>
<!-- Embed by IPFS CID -->
<iframe
src="https://audio.3speak.tv/play?cid=QmdMsEXyDe5Z4S3n8THfYgFP1iH3ngCeCytdHnndzqdZAK"
width="100%"
height="120"
frameborder="0">
</iframe>
Display Modes
| Mode | Height | Description |
|---|---|---|
minimal |
80px | Play button, waveform, time only |
compact |
120px | Default - full controls without metadata |
full |
180px | All controls + title, owner, plays |
<iframe src="https://audio.3speak.tv/play?a=abc123&mode=minimal" height="80"></iframe>
Iframe Mode (No Scrollbars)
Add &iframe=1 for clean embedding without scrollbars - perfect for chat apps:
<!-- Clean embed with no scrollbars -->
<iframe
src="https://audio.3speak.tv/play?a=abc123&mode=compact&iframe=1"
width="100%"
height="120"
frameborder="0"
scrolling="no"
allow="autoplay">
</iframe>
iframe=1 to remove all padding and prevent scrollbars. Ideal for seamless integration in chat apps or tight spaces.
📤 Upload API
/api/audio/upload
Upload audio files to IPFS with metadata indexing.
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key |
Yes | Your API key |
X-User |
Yes | Username (required for all uploads) |
Content-Type |
Yes | multipart/form-data |
Form Data Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
audio |
File | Yes | Audio file (mp3, m4a, ogg, webm, wav) |
duration |
Number | Yes | Duration in seconds |
format |
String | Yes | File format (mp3, m4a, etc.) |
title |
String | No | Audio title |
description |
String | No | Audio description |
tags |
JSON Array | No | Array of tag strings for content discovery (e.g., ["music", "podcast"]) |
codec |
String | No | Audio codec (aac, mp3, etc.) |
bitrate |
Number | No | Bitrate in kbps |
sampleRate |
Number | No | Sample rate in Hz |
channels |
Number | No | Number of channels (1=mono, 2=stereo) |
waveform |
JSON | No | Waveform data object |
thumbnail_url |
String (URL) | No | Cover art/thumbnail URL (max 2048 chars, http/https only) |
category |
String | No | Content type: voice_message (default), podcast, song, interview, audiobook, noise_sample |
post_permlink |
String | No | Blockchain post reference (e.g., Hive permlink) for linking comments/reactions (max 256 chars) |
- Max file size: 50MB
- Supported formats: mp3, m4a, ogg, webm, wav
- Rate limit: 10 uploads per minute
- Username required: X-User header must be provided
- Banned users: Users with upload restrictions will receive 403 Forbidden
Response
{
"success": true,
"permlink": "w2ehm8pr",
"cid": "QmdMsEXyDe5Z4S3n8THfYgFP1iH3ngCeCytdHnndzqdZAK",
"playUrl": "https://audio.3speak.tv/play?a=w2ehm8pr",
"apiUrl": "https://audio.3speak.tv/api/audio?a=w2ehm8pr"
}
▶️ Playback API
/api/audio
Retrieve audio metadata for playback. No authentication required.
Query Parameters
| Parameter | Description | Example |
|---|---|---|
a |
Audio permlink | ?a=w2ehm8pr |
cid |
IPFS Content ID | ?cid=QmdMsEX... |
Response
{
"permlink": "w2ehm8pr",
"owner": "meno",
"audio_cid": "QmdMsEXyDe5Z4S3n8THfYgFP1iH3ngCeCytdHnndzqdZAK",
"category": "voice_message",
"duration": 45,
"format": "mp3",
"bitrate": 128,
"sampleRate": 44100,
"channels": 1,
"waveform": {
"peaks": [0.05, 0.34, 0.93, ...],
"samples": 100
},
"audioUrl": "http://gateway.ipfs/ipfs/QmdMsEX...",
"audioUrlFallback": "https://ipfs.io/ipfs/QmdMsEX...",
"title": "My Audio",
"description": "This is an example audio recording",
"tags": ["music", "podcast", "interview"],
"thumbnail_url": "https://files.hive.blog/file/hiveimages/abc123.jpg",
"post_permlink": "my-audio-snap-2026",
"plays": 42,
"likes": 5,
"createdAt": "2025-11-27T01:22:17.053Z"
}
Get Audio Feed
/api/audio/feed
Get a paginated feed of audio content with filtering and sorting options. Perfect for building frontends with discovery feeds, trending pages, tag browsing, and user profiles. No authentication required.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
Number | 20 | Items per page (max 100) |
offset |
Number | 0 | Pagination offset |
sort |
String | newest | Sort order: newest, oldest, plays, trending |
category |
String | - | Filter by category: voice_message, podcast, song, interview, audiobook, noise_sample |
tag |
String | - | Filter by tag (e.g., "music", "podcast") |
owner |
String | - | Filter by username (user's audio only) |
Example Requests
# Latest audio (newest first)
GET /api/audio/feed?limit=20&offset=0&sort=newest
# Trending audio (most played)
GET /api/audio/feed?sort=plays
# All podcasts, sorted by newest
GET /api/audio/feed?category=podcast&sort=newest
# Audio tagged with "music"
GET /api/audio/feed?tag=music&limit=50
# User's audio profile
GET /api/audio/feed?owner=meno&sort=newest
# Paginated results (page 2)
GET /api/audio/feed?limit=20&offset=20
Response
{
"items": [
{
"permlink": "abc123xy",
"owner": "meno",
"audio_cid": "QmdMsEX...",
"category": "podcast",
"duration": 1800,
"format": "mp3",
"title": "My Podcast Episode",
"description": "Episode description here",
"tags": ["podcast", "tech", "interview"],
"thumbnail_url": "https://files.hive.blog/.../cover.jpg",
"post_permlink": "my-podcast-ep1",
"plays": 152,
"likes": 23,
"createdAt": "2026-02-20T10:30:00Z",
"audioUrl": "https://ipfs.3speak.tv/ipfs/QmdMsEX...",
"audioUrlFallback": "https://ipfs.io/ipfs/QmdMsEX..."
},
// ... more items
],
"pagination": {
"limit": 20,
"offset": 0,
"total": 156,
"hasMore": true
},
"filters": {
"sort": "newest",
"category": null,
"tag": null,
"owner": null
}
}
- Home Feed:
?sort=newest&limit=20 - Trending Page:
?sort=plays&limit=50 - Tag Browser:
?tag=music&sort=plays - User Profile:
?owner=username&sort=newest - Category Page:
?category=podcast&sort=newest
Update Thumbnail
/api/audio/:permlink/thumbnail
Update the thumbnail URL for an existing audio. Authentication required. Only the audio owner can update their thumbnails.
Request Body
PATCH /api/audio/w2ehm8pr/thumbnail
Content-Type: application/json
X-API-Key: sk_your_api_key
X-User: meno
{
"thumbnail_url": "https://files.hive.blog/file/hiveimages/new-image.jpg"
}
Response
{
"success": true,
"permlink": "w2ehm8pr",
"thumbnail_url": "https://files.hive.blog/file/hiveimages/new-image.jpg"
}
- URL must be valid (http:// or https://)
- Maximum length: 2048 characters
- Only owner can update (validated via X-User header)
Update Blockchain Post Permlink
/api/audio/:permlink/post-permlink
Update the blockchain post permlink for an existing audio. Authentication required. Only the audio owner can update their post permlink.
Request Body
PATCH /api/audio/w2ehm8pr/post-permlink
Content-Type: application/json
X-API-Key: sk_your_api_key
X-User: meno
{
"post_permlink": "my-audio-snap-2026"
}
Response
{
"success": true,
"permlink": "w2ehm8pr",
"post_permlink": "my-audio-snap-2026"
}
- Must be alphanumeric with hyphens/underscores only
- Maximum length: 256 characters
- Only owner can update (validated via X-User header)
Track Play Count
/api/audio/play
Increment play counter when audio starts playing.
POST /api/audio/play
Content-Type: application/json
{
"permlink": "w2ehm8pr"
}
Response:
{
"success": true,
"plays": 43
}
💻 Code Examples
JavaScript (Fetch API)
// Upload audio file
async function uploadAudio(file) {
const formData = new FormData();
formData.append('audio', file);
formData.append('duration', 45);
formData.append('format', 'mp3');
formData.append('title', 'My Recording');
formData.append('thumbnail_url', 'https://files.hive.blog/file/hiveimages/cover.jpg');
formData.append('category', 'podcast');
formData.append('post_permlink', 'my-audio-snap-2026');
formData.append('tags', JSON.stringify(['music', 'podcast']));
const response = await fetch('https://audio.3speak.tv/api/audio/upload', {
method: 'POST',
headers: {
'X-API-Key': 'sk_your_api_key',
'X-User': 'username'
},
body: formData
});
return await response.json();
}
// Get audio metadata
async function getAudio(permlink) {
const response = await fetch(
`https://audio.3speak.tv/api/audio?a=${permlink}`
);
return await response.json();
}
// Get audio feed
async function getFeed(options = {}) {
const {
limit = 20,
offset = 0,
sort = 'newest',
category,
tag,
owner
} = options;
const params = new URLSearchParams({
limit,
offset,
sort
});
if (category) params.append('category', category);
if (tag) params.append('tag', tag);
if (owner) params.append('owner', owner);
const response = await fetch(
`https://audio.3speak.tv/api/audio/feed?${params}`
);
return await response.json();
}
// Example: Get trending podcasts
async function getTrendingPodcasts() {
return await getFeed({
category: 'podcast',
sort: 'plays',
limit: 50
});
}
// Example: Get user's audio
async function getUserAudio(username) {
return await getFeed({
owner: username,
sort: 'newest'
});
}
// Track play
async function trackPlay(permlink) {
await fetch('https://audio.3speak.tv/api/audio/play', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ permlink })
});
}
// Update thumbnail
async function updateThumbnail(permlink, thumbnailUrl) {
const response = await fetch(
`https://audio.3speak.tv/api/audio/${permlink}/thumbnail`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'sk_your_api_key',
'X-User': 'username'
},
body: JSON.stringify({ thumbnail_url: thumbnailUrl })
}
);
return await response.json();
}
// Update Post Permlink
async function updatePostPermlink(permlink, postPermlink) {
const response = await fetch(
`https://audio.3speak.tv/api/audio/${permlink}/post-permlink`,
{
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-API-Key': 'sk_your_api_key',
'X-User': 'username'
},
body: JSON.stringify({ post_permlink: postPermlink })
}
);
return await response.json();
}
cURL
# Upload audio
curl -X POST https://audio.3speak.tv/api/audio/upload \
-H "X-API-Key: sk_your_api_key" \
-H "X-User: username" \
-F "audio=@recording.mp3" \
-F "duration=45" \
-F "format=mp3" \
-F "title=My Recording" \
-F "thumbnail_url=https://files.hive.blog/file/hiveimages/cover.jpg" \
-F "category=podcast" \
-F "post_permlink=my-audio-snap-2026" \
-F "tags=[\"music\",\"podcast\"]"
# Get metadata
curl "https://audio.3speak.tv/api/audio?a=w2ehm8pr"
# Get feed (latest audio)
curl "https://audio.3speak.tv/api/audio/feed?limit=20&offset=0&sort=newest"
# Get trending podcasts
curl "https://audio.3speak.tv/api/audio/feed?category=podcast&sort=plays&limit=50"
# Get audio tagged with 'music'
curl "https://audio.3speak.tv/api/audio/feed?tag=music"
# Get user's audio
curl "https://audio.3speak.tv/api/audio/feed?owner=meno&sort=newest"
# Track play
curl -X POST https://audio.3speak.tv/api/audio/play \
-H "Content-Type: application/json" \
-d '{"permlink":"w2ehm8pr"}'
# Update thumbnail
curl -X PATCH https://audio.3speak.tv/api/audio/w2ehm8pr/thumbnail \
-H "X-API-Key: sk_your_api_key" \
-H "X-User: username" \
-H "Content-Type: application/json" \
-d '{"thumbnail_url":"https://files.hive.blog/file/hiveimages/new.jpg"}'
# Update post permlink
curl -X PATCH https://audio.3speak.tv/api/audio/w2ehm8pr/post-permlink \
-H "X-API-Key: sk_your_api_key" \
-H "X-User: username" \
-H "Content-Type: application/json" \
-d '{"post_permlink":"my-audio-snap-2026"}'
Python
import requests
# Upload audio
def upload_audio(file_path, api_key):
with open(file_path, 'rb') as f:
files = {'audio': f}
data = {
'duration': 45,
'format': 'mp3',
'title': 'My Recording',
'thumbnail_url': 'https://files.hive.blog/file/hiveimages/cover.jpg',
'category': 'podcast',
'post_permlink': 'my-audio-snap-2026',
'tags': '["music", "podcast"]'
}
headers = {
'X-API-Key': api_key,
'X-User': 'username'
}
response = requests.post(
'https://audio.3speak.tv/api/audio/upload',
files=files,
data=data,
headers=headers
)
return response.json()
# Get metadata
def get_audio(permlink):
response = requests.get(
f'https://audio.3speak.tv/api/audio?a={permlink}'
)
return response.json()
# Get feed with filters
def get_feed(limit=20, offset=0, sort='newest', category=None, tag=None, owner=None):
params = {
'limit': limit,
'offset': offset,
'sort': sort
}
if category:
params['category'] = category
if tag:
params['tag'] = tag
if owner:
params['owner'] = owner
response = requests.get(
'https://audio.3speak.tv/api/audio/feed',
params=params
)
return response.json()
# Example: Get trending podcasts
def get_trending_podcasts():
return get_feed(category='podcast', sort='plays', limit=50)
# Example: Get user's audio
def get_user_audio(username):
return get_feed(owner=username, sort='newest')
⏱️ Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
Upload /api/audio/upload |
10 requests | 1 minute |
Play tracking /api/audio/play |
100 requests | 1 minute |
Metadata /api/audio |
Unlimited | - |
Feed /api/audio/feed |
Unlimited | - |
X-RateLimit-Limit- Max requests allowedX-RateLimit-Remaining- Remaining requestsX-RateLimit-Reset- Time when limit resets (Unix timestamp)
❌ Error Handling
HTTP Status Codes
| Code | Meaning | Description |
|---|---|---|
200 |
OK | Request successful |
201 |
Created | Upload successful |
400 |
Bad Request | Invalid parameters or file format |
401 |
Unauthorized | Missing or invalid API key |
403 |
Forbidden | User is banned or lacks upload permissions |
404 |
Not Found | Audio not found |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Server error |
Error Response Format
{
"error": "Invalid file format",
"message": "Allowed formats: mp3, m4a, ogg, webm, wav"
}
Common Errors
{
"error": "Invalid API key",
"message": "The provided API key is not valid"
}
{
"error": "File too large",
"message": "Maximum file size is 50MB"
}
{
"error": "Too many requests, please try again later"
}
{
"error": "Upload not allowed",
"message": "User is banned from uploading"
}
{
"error": "Username required",
"message": "X-User header must be provided for uploads"
}