v1.2.0 • Project Nova

The Music Meta.
No Gatekeepers.

Search, stream, and manage your music meta with a single command. Project Nova is a local-first ecosystem featuring the novam CLI and Nova Player UI.

$ curl -sSL https://nesbes.me/install.sh | bash

Quick Start

Get Nova running in under 60 seconds. Zero configuration required.

INSTALL One-line installer
curl -sSL https://nesbes.me/install.sh | bash
CLI Start the Dashboard
# Start API + open dashboard
./novam dashboard

# Or just start API (headless)
./novam api

# Update to latest version
./novam update
BASE URL API Endpoint

http://localhost:5001/api/v1

All endpoints are prefixed with /api/v1. No API key required in development mode (Zero-Gate access).

Tracks & Streaming

Stream audio and retrieve detailed track metadata. The stream endpoint returns raw audio data.

STREAM /api/v1/tracks/stream/{id}

Returns raw audio stream. Content-Type: audio/webm or audio/m4a.
Use in <audio src="..."> or download directly.

# Stream to file (bash)
curl -o song.m4a "http://localhost:5001/api/v1/tracks/stream/4NhPJTK8cAq7"

# Python download
import requests
r = requests.get(f"{API}/api/v1/tracks/stream/{track_id}", stream=True)
with open("song.m4a", "wb") as f:
    for chunk in r.iter_content(8192):
        f.write(chunk)
GET /api/v1/tracks/{id}

Returns full track metadata including duration, album art, and artist info.

{
  "data": {
    "id": "4NhPJTK8cAq7",
    "name": "Blinding Lights",
    "duration_ms": 200040,
    "artists": [{"id": "...", "name": "The Weeknd"}],
    "album": {
      "name": "After Hours",
      "images": [{"url": "https://..."}]
    },
    "thumbnail": "https://i.ytimg.com/vi/.../maxresdefault.jpg"
  }
}
GET /api/v1/tracks/{id}/thumbnail

Returns high-resolution album artwork. Redirects to image URL.

Lyrics API

Fetch song lyrics. Supports auto-transcription via Whisper AI for tracks without lyrics.

GET /api/v1/lyrics/{track_id}
{
  "data": {
    "track_id": "4NhPJTK8cAq7",
    "lyrics": "I've been tryna call\nI've been on my own for long enough...",
    "source": "genius",
    "synced": false
  }
}
POST /api/v1/lyrics/{track_id}/transcribe

AI-powered transcription using Whisper. Generates lyrics from audio when none are available.
Note: Takes 10-30 seconds depending on track length.

Browse & Discovery

Explore trending music, genres, and get AI-powered recommendations.

GET /api/v1/browse/trending

Query: ?region=US (ISO country code)

Returns trending tracks for the specified region.

GET /api/v1/browse/genres

Returns all available genre categories with playlist IDs.

GET /api/v1/browse/recommendations/{track_id}

Get similar tracks based on a seed track. Great for "radio" features.

Artists API

Get artist profiles, top tracks, albums, and related artists.

GET /api/v1/artists/{id}
{
  "data": {
    "id": "1Xyo4u8uXC1ZmMpatF05PJ",
    "name": "The Weeknd",
    "description": "Canadian singer-songwriter...",
    "images": [{"url": "https://..."}],
    "subscribers": "35M"
  }
}
GET /api/v1/artists/{id}/top-tracks

Returns the artist's most popular tracks.

GET /api/v1/artists/{id}/albums

Returns all albums and singles by the artist.

Playlists

Create, manage, and share playlists. Get tracks from existing playlists.

GET /api/v1/playlists/{id}

Get playlist details and all tracks.

POST /api/v1/playlists

Body:

{
  "name": "My Playlist",
  "description": "Best vibes",
  "tracks": ["track_id_1", "track_id_2"]
}

Data Schemas

Complete TypeScript/JSON schemas for all data types. Feed this to your AI.

// TypeScript Schemas for Nova API

interface Track {
  id: string;                    // Unique track identifier
  name: string;                  // Track title
  duration_ms: number;           // Duration in milliseconds
  explicit: boolean;             // Explicit content flag
  artists: Artist[];             // Array of artists
  album: Album;                  // Album info
  thumbnail?: string;            // Direct image URL
}

interface Artist {
  id: string;                    // Unique artist identifier
  name: string;                  // Artist/band name
  images?: Image[];              // Profile images
  description?: string;          // Bio/description
  subscribers?: string;          // Subscriber count
}

interface Album {
  id: string;                    // Unique album identifier
  name: string;                  // Album title
  images: Image[];               // Cover art (multiple sizes)
  release_date?: string;         // Release date (YYYY-MM-DD)
  total_tracks?: number;         // Number of tracks
}

interface Image {
  url: string;                   // Image URL
  width: number;                 // Width in pixels
  height: number;                // Height in pixels
}

interface SearchResponse {
  success: boolean;
  data: {
    total: number;               // Total results found
    tracks: Track[];             // Array of tracks
  }
}

interface Lyrics {
  track_id: string;
  lyrics: string;                // Full lyrics text
  source: string;                // "genius" | "transcribed"
  synced: boolean;               // Time-synced lyrics available
}

interface ErrorResponse {
  success: false;
  error: {
    code: string;                // Error code (see Error Handling)
    message: string;             // Human-readable message
  }
}

Code Examples

Copy-paste examples for common use cases. Perfect for AI-assisted development.

PYTHON Search & Download
import requests

API = "http://localhost:5001/api/v1"

# Search for a song
results = requests.get(f"{API}/search", params={"q": "Blinding Lights"}).json()
track = results["data"]["tracks"][0]

print(f"Found: {track['name']} by {track['artists'][0]['name']}")

# Download the track
audio = requests.get(f"{API}/tracks/stream/{track['id']}", stream=True)
with open(f"{track['name']}.m4a", "wb") as f:
    for chunk in audio.iter_content(8192):
        f.write(chunk)

print("Download complete!")
JS Browser Player
const API = "http://localhost:5001/api/v1";

async function playTrack(query) {
  // Search
  const res = await fetch(`${API}/search?q=${encodeURIComponent(query)}`);
  const { data } = await res.json();
  const track = data.tracks[0];

  // Play in audio element
  const audio = new Audio(`${API}/tracks/stream/${track.id}`);
  audio.play();

  // Show info
  console.log(`Now playing: ${track.name} by ${track.artists[0].name}`);
  console.log(`Album art: ${track.album.images[0].url}`);
}

playTrack("Starboy");
cURL Quick Commands
# Search
curl "http://localhost:5001/api/v1/search?q=Drake&limit=5"

# Get track info
curl "http://localhost:5001/api/v1/tracks/4NhPJTK8cAq7"

# Download audio
curl -o song.m4a "http://localhost:5001/api/v1/tracks/stream/4NhPJTK8cAq7"

# Get lyrics
curl "http://localhost:5001/api/v1/lyrics/4NhPJTK8cAq7"

# Get recommendations
curl "http://localhost:5001/api/v1/browse/recommendations/4NhPJTK8cAq7"

Error Handling

All errors return JSON with a consistent structure. Use the code for programmatic handling.

{
  "success": false,
  "error": {
    "code": "TRACK_NOT_FOUND",
    "message": "The requested track could not be found."
  }
}
Code HTTP Description
MISSING_QUERY 400 The 'q' query parameter is required for search.
INVALID_LIMIT 400 Limit must be between 1 and 50.
TRACK_NOT_FOUND 404 Track ID does not exist or is unavailable.
ARTIST_NOT_FOUND 404 Artist ID does not exist.
PLAYLIST_NOT_FOUND 404 Playlist ID does not exist or is private.
LYRICS_NOT_FOUND 404 No lyrics available. Consider using /transcribe.
STREAM_ERROR 500 Failed to fetch audio stream from source.
RATE_LIMITED 429 Too many requests. Wait and retry.
UPSTREAM_ERROR 502 Source service is temporarily unavailable.