Back to the catalog

Uncommon Ear API

Search free music and sound effects from your own code. Every result tells you its license and terms, whether credit is asked or required, and gives you a credit line to paste.

The API is a read-only JSON API. It needs a free Uncommon Ear account and an API key. To search from an AI assistant instead, see Use Uncommon Ear from your AI assistant. The routes are described in an OpenAPI 3.1 document, which you can load into tools such as Postman or a code generator.

Get a key

  1. Sign in to Uncommon Ear. A free account is all you need. If you don't have one yet, signing in creates it.
  2. Open the account menu and select API keys.
  3. Enter a name, such as Build script, and select Create key.
  4. Copy the key, which starts with ue_. For your security, it's shown only once. If you lose it, revoke it and create another.

You can have up to 5 active keys. Revoking a key stops it working right away, and deleting your account deletes all of your keys. Treat a key like a password: keep it out of source control and out of web pages, and store it in an environment variable or a secrets manager. Send it only in the Authorization header. The API doesn't use cookies, and it doesn't accept a key in the URL.

Make your first request

Every request is a GET to https://uncommonear.com with your key in the Authorization: Bearer header. This request finds three calm piano tracks.

curl

Shell
export UNCOMMON_EAR_API_KEY="ue_your_key_here"

curl "https://uncommonear.com/v1/search?q=calm+piano&limit=3" \
  -H "Authorization: Bearer $UNCOMMON_EAR_API_KEY"

JavaScript

This example uses fetch, which is built into Node.js 18 and later and into every browser. Don't call the API from a web page, because that would expose your key to every visitor. Call it from a server or a script.

JavaScript (Node.js)
const key = process.env.UNCOMMON_EAR_API_KEY;
const url = "https://uncommonear.com/v1/search?" + new URLSearchParams({ q: "calm piano", limit: "3" });

const response = await fetch(url, {
  headers: { Authorization: "Bearer " + key },
});
if (!response.ok) {
  const { error, message } = await response.json();
  throw new Error(error + ": " + message);
}
const { tracks, cursor } = await response.json();
for (const track of tracks) {
  console.log(track.title, "-", track.credit_line);
}

Python

This example uses the requests library. Install it with pip install requests.

Python
import os
import requests

response = requests.get(
    "https://uncommonear.com/v1/search",
    params={"q": "calm piano", "limit": 3},
    headers={"Authorization": "Bearer " + os.environ["UNCOMMON_EAR_API_KEY"]},
    timeout=10,
)
response.raise_for_status()
for track in response.json()["tracks"]:
    print(track["title"], "-", track["credit_line"])

A successful call returns status 200 and JSON like this:

Response
{
  "tracks": [
    {
      "id": "fp-0000000001",
      "title": "Sunrise Walk",
      "artist": "Kevin MacLeod",
      "kind": "music",
      "duration_s": 90,
      "bpm": 100,
      "tags": { "genre": ["cinematic"], "mood": [], "use": [], "instrument": [], "sfx": [] },
      "license": { "id": "cc0", "name": "CC0 1.0", "url": "https://creativecommons.org/publicdomain/zero/1.0/", "status": "verified" },
      "terms": { "commercial": true, "credit": "none", "share_alike": false },
      "attribution": "requested",
      "credit_line": "\"Sunrise Walk\" by Kevin MacLeod, via FreePD (CC0 1.0) https://freepd.com/...",
      "source_page": "https://freepd.com/...",
      "preview_url": "https://media.uncommonear.com/p/fp-0000000001.mp3",
      "page_url": "https://uncommonear.com/browse?t=fp-0000000001"
    }
  ],
  "total": 1,
  "cursor": null
}

Tracks and similar tracks

GET /v1/tracks/{id} returns one track, in the same shape as an item in tracks. Use the id from a search result.

Get one track
curl "https://uncommonear.com/v1/tracks/fp-0000000001" \
  -H "Authorization: Bearer $UNCOMMON_EAR_API_KEY"

GET /v1/tracks/{id}/similar returns { "tracks": [...] }: tracks of the same kind that sound like this one, closest first. It takes one parameter, limit (1 to 50; default 20). Both routes return a 404 error when no track has the id.

Find similar tracks
curl "https://uncommonear.com/v1/tracks/fp-0000000001/similar?limit=5" \
  -H "Authorization: Bearer $UNCOMMON_EAR_API_KEY"

Response fields

Each track has these fields. We may add fields later, so ignore the ones you don't use.

Track fields
FieldTypeDescription
idstringThe track id. Use it with the track and similar routes.
titlestringThe track title.
artiststringThe artist. Can be empty when the source gives none.
kindstringmusic or sfx.
duration_snumberLength in seconds.
bpmnumber or nullBeats per minute, or null when the track has no steady tempo.
tagsobjectLists of genre, mood, use, instrument, and sfx tags.
licenseobjectid (such as cc-by-4.0), name (such as CC BY 4.0), url (the license text), and status: verified (we checked the source page) or listed (the collection says so, and we have not checked).
termsobjectWhat the license lets you do: commercial (true or false), credit (none or required), and share_alike (true or false).
attributionstringoptional, requested, or required. required means the license requires credit. requested means the license does not, but the creator or source asks. See the next section.
credit_linestringA ready-to-paste credit line. Use it when attribution is requested, or whenever you want to give credit.
source_pagestring or nullThe page where the source publishes the track.
preview_urlstring or nullA URL you can play. Previews are lower quality than the original.
page_urlstringA link to the track in the Uncommon Ear catalog.

Attribution and credit lines

Tracks come under different licenses, and each result carries its license and terms. CC0 tracks need no permission and no credit, including in commercial work. Other licenses can require credit, require you to share what you make under the same license (share-alike), or rule out commercial use. The attribution field tells you which credit case you're in:

  • optional: nobody asks for credit.
  • requested: the license doesn't require credit, but the creator or source asks. Include the credit_line wherever you publish the track.
  • required: the license requires credit. Include the credit_line, which names the license and links its text.

To find only the tracks that need no credit, search with credit=optional. To find the ones that ask for it or require it, use credit=requested. To find tracks you can use in commercial work, use commercial=true, and to skip share-alike tracks, use share_alike=false.

Errors

Every error is JSON with two fields: error, a short code to check in your program, and message, an explanation in plain words.

Example: 400
{
  "error": "invalid_parameter",
  "message": "The kind parameter must be one of music, sfx."
}
Errors
StatusCodeMeaning
400invalid_parameterA parameter is not valid or not recognized. The message names it.
401missing_api_keyThe request has no Authorization header.
401invalid_api_keyThe key is not valid, or it was revoked.
404not_foundNo track has that id, or the route does not exist.
405method_not_allowedThe API only answers GET requests.
429rate_limitedOver the limit. The Retry-After header gives the seconds to wait.
500internal_errorSomething went wrong on our side. Try again soon.
503unavailableThe catalog is not available right now. Try again soon.

The two 401 errors have the same fix: send a valid key. For a revoked key, create a new one under API keys in the account menu.

Limits

The limits depend on your plan:

Limits by plan
PlanPer minutePer dayPer month
Free20 calls300 callsNo limit
Pro60 callsNo limit20,000 calls

Free accounts can make 20 calls per minute and 300 calls per day. Pro accounts can make 60 calls per minute and 20,000 calls per month, with no daily limit. A month is a calendar month in UTC. The limit belongs to your account, not to a key, and it's shared with the MCP server: calls from your keys and from your connected assistants count together. Calls that fail with a 400 or 404 error count too.

Over a limit, the API returns status 429 with a Retry-After header that gives the number of seconds to wait, up to 86,400. The response body also has limit (minute, day, or month) and reset_at, the UTC time when that limit resets. Wait that long before you retry, and spread a long job over time instead of sending bursts. Calls with a missing or invalid key return 401 and don't count against any account.

What we record

For each call, we record your account, which key made it, the route, whether it worked, how long it took, and how many results it returned. For searches, we also record the search text. We never store the key itself or the Authorization header, and we don't record your IP address or the results. We remove the search text after 90 days and the rest after 400 days. Read the details in the privacy policy.

Coming with Pro

Make clips and save to collections.