> ## Documentation Index
> Fetch the complete documentation index at: https://docs.roadshop.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Music API

> Serve your own music library to the Music app from your own HTTP server

# Custom Music API

By default, the Music app searches the built-in library (`roadshop_music_library`), which is filled through player submissions and the in-app admin panel. With the **Custom Music API** you can replace that library entirely and serve songs from your own HTTP server.

## How It Works

```
┌─────────────┐        ┌──────────────┐         ┌──────────────────┐
│  Car Radio   │        │  FiveM       │         │  Your Music      │
│  NUI         │        │  Server      │         │  API Server      │
└─────┬────────┘        └──────┬───────┘         └────────┬─────────┘
      │  1. search "query"     │                          │
      │ ─────────────────────> │  2. GET ?search=query    │
      │                        │ ───────────────────────> │
      │                        │                          │  3. Filter songs
      │                        │  4. JSON song array      │
      │                        │ <─────────────────────── │
      │  5. search results     │                          │
      │ <───────────────────── │                          │
      │                        │                          │
      │  6. Player taps a song → plays via YouTube id or direct URL
```

<CardGroup cols={2}>
  <Card title="Server-Side Only" icon="shield-half">
    The request runs on the FiveM server via `PerformHttpRequest`. Your API key **never leaves the server** — the NUI frontend never sees your endpoint or credentials.
  </Card>

  <Card title="No Frontend Changes" icon="car">
    The Music app works exactly as before: search, playlists and playback are untouched. Only the data source changes.
  </Card>

  <Card title="Unlimited Catalog" icon="infinity">
    Every search hits your server live, so your catalog can be as large as you want — your server does the filtering.
  </Card>

  <Card title="YouTube & Direct Files" icon="file-audio">
    Each song is either a YouTube video id or a direct link to an audio file (mp3/ogg) on your own CDN.
  </Card>
</CardGroup>

<Warning>
  While the Custom Music API is enabled, the built-in music library, song submissions and the music admin panel are **bypassed**. Songs already saved in player playlists keep working, since playlists store the song id and type directly.
</Warning>

***

## Configuration

Edit `MusicAPI.lua` in the resource root (it is `escrow_ignore`, so you can edit it freely):

```lua MusicAPI.lua theme={null}
MusicAPI.Enabled = true                                 -- Load music from your own server
MusicAPI.URL = 'https://your-server.com/api/music'      -- Your music search endpoint
MusicAPI.APIKey = 'your-secret-key'                     -- Optional, sent as Authorization header
```

No `config.lua` changes and no frontend rebuild needed. Restart the resource after editing.

***

## Request Format

For every search in the Music app, RoadCarRadio sends:

```
GET <MusicAPI.URL>?search=<urlencoded search term>
Authorization: <MusicAPI.APIKey>
```

* The `Authorization` header is only sent when `MusicAPI.APIKey` is a non-empty string.
* An **empty** `search` parameter is sent when the player opens the search view. Return your newest or featured songs in that case.

***

## Response Format

Respond with **HTTP 200** and a JSON array of songs. At most the **first 50** are used:

```json theme={null}
[
  {
    "id": "dQw4w9WgXcQ",
    "url_type": "youtube",
    "title": "Never Gonna Give You Up",
    "artist": "Rick Astley",
    "thumbnail": "https://your-cdn.example.com/covers/rick.jpg",
    "length": "3:32"
  },
  {
    "id": "https://your-cdn.example.com/songs/track.mp3",
    "url_type": "direct",
    "title": "My Custom Track",
    "artist": "Server DJ",
    "thumbnail": "https://your-cdn.example.com/covers/track.jpg",
    "length": "2:45"
  }
]
```

Wrapping the array in an object is also supported: `{ "songs": [ ... ] }`

### Song Fields

| Field       | Required | Description                                                                                                          |
| ----------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `id`        | ✅        | YouTube video id when `url_type` is `youtube`, or a full `https://` URL to an audio file when `url_type` is `direct` |
| `title`     | ✅        | Song title shown in the app                                                                                          |
| `artist`    | ❌        | Artist name — defaults to an empty string                                                                            |
| `url_type`  | ❌        | `"youtube"` or `"direct"`. Anything other than `"direct"` is treated as `"youtube"`                                  |
| `thumbnail` | ❌        | Cover image URL                                                                                                      |
| `length`    | ❌        | Song length as `mm:ss` — defaults to `0:00`                                                                          |

<Tip>
  **Already have an endpoint for another RoadShop resource?** RoadCarRadio also accepts the interface's own field names, so you can point it at an existing feed without rewriting it:

  | Standard    | Also accepted |
  | ----------- | ------------- |
  | `title`     | `songname`    |
  | `artist`    | `name`        |
  | `thumbnail` | `icon`        |
  | `length`    | `songlength`  |

  If both are present, the standard name wins.
</Tip>

<Info>
  A song is skipped when it has no `id`, or neither `title` nor `songname`. On any error — non-200 status, invalid JSON, or a response with no array — the app shows an empty result list and a warning is printed to the FiveM server console.
</Info>

***

## Example: Building Your Own Endpoint

A minimal music API using Node.js and Express:

```javascript theme={null}
const express = require('express')
const app = express()

const API_KEY = 'your-secret-key'

const LIBRARY = [
  {
    id: 'dQw4w9WgXcQ',
    url_type: 'youtube',
    title: 'Never Gonna Give You Up',
    artist: 'Rick Astley',
    thumbnail: 'https://your-cdn.example.com/covers/rick.jpg',
    length: '3:32',
  },
  {
    id: 'https://your-cdn.example.com/songs/track.mp3',
    url_type: 'direct',
    title: 'My Custom Track',
    artist: 'Server DJ',
    thumbnail: 'https://your-cdn.example.com/covers/track.jpg',
    length: '2:45',
  },
]

app.get('/api/music', (req, res) => {
  // Validate API key (skip this block if you don't use one)
  if (req.headers.authorization !== API_KEY) {
    return res.status(401).json({ error: 'Unauthorized' })
  }

  const search = (req.query.search || '').toLowerCase()

  // Empty search: return newest/featured songs
  if (!search) {
    return res.json(LIBRARY.slice(0, 50))
  }

  const results = LIBRARY.filter(
    (song) =>
      song.title.toLowerCase().includes(search) ||
      song.artist.toLowerCase().includes(search)
  )

  res.json(results.slice(0, 50))
})

app.listen(3000)
```

<Note>
  In production, serve the library from a database, add rate limiting, and cache popular queries. Direct audio files must be publicly reachable from every player's game client — they are streamed by xSound/mx-surround on the client, not proxied through your FiveM server.
</Note>

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="Search always shows an empty list">
    * Check the FiveM server console for `[RoadCarRadio] MusicAPI ...` warnings — they name the exact failure
    * Verify your endpoint is reachable **from the FiveM server** (try `curl` on that machine, not from your desktop)
    * Confirm the response is a JSON array, or `{ "songs": [...] }`, with `id` and `title` on every song
    * Make sure the API key in `MusicAPI.lua` matches what your endpoint expects
  </Accordion>

  <Accordion title="Songs appear but won't play">
    * For `url_type: "youtube"`, `id` must be the plain video id (e.g. `dQw4w9WgXcQ`), not a full URL
    * For `url_type: "direct"`, `id` must be a full `https://` URL to an audio file that players' game clients can reach
    * Test a direct URL in your browser — it should stream the audio without a login
    * Confirm `Config.AudioLibrary` matches the audio resource you actually run (`xsound` or `mxsurround`)
  </Accordion>

  <Accordion title="The built-in library still shows up">
    * Ensure `MusicAPI.Enabled = true` in `MusicAPI.lua` in the resource root
    * Restart the resource completely after changing the file
    * Check that `MusicAPI.lua` is listed in `fxmanifest.lua` under `server_scripts`
  </Accordion>

  <Accordion title="Only some of my songs show up">
    * At most **50** songs per response are used. Filter server-side and return the best matches rather than your whole catalog
    * Songs without an `id`, or without both `title` and `songname`, are skipped silently
  </Accordion>
</AccordionGroup>

***

## Related

<CardGroup cols={2}>
  <Card title="Installation" icon="download" href="/roadcarradio/installation">
    Requirements, setup and configuration
  </Card>

  <Card title="Commands" icon="terminal" href="/roadcarradio/commands">
    Including the music approval command
  </Card>
</CardGroup>
