● v1 spec in development
API icon

basehead Local API v1

We’re building a local API for basehead right now… so your own tools can search a library, import new files and transfer sounds while basehead keeps doing its thing.

How it works

Local onlyHTTP server inside basehead on 127.0.0.1. Off by default… enabled in Options → API Keys → Local API. Default port 12120.
basehead must be runningThe API lives inside the app. If basehead is closed, requests fail to connect.
HeadlessCalls return data and never touch the UI… except select (shows files in Results) and import (basehead’s normal import progress, then the new files in Results). Every call also pops a purple ‘Local API’ notice in basehead with a Turn Off button, so the user always sees outside traffic.
MCP tooThe same commands are exposed as an MCP server so AI agents can call basehead directly. Connect your agent to http://127.0.0.1:12120/mcp with the same token.
One command layerbasehead’s own features are moving onto these same commands… one code path, no private shortcuts.
EditionUltra only. In development… not released yet.

Auth & requests

A token is shown in Options → API Keys → Local API. Copy it into your tool once and send it with every request. JSON in, JSON out. All paths are versioned under /api/v1/.

# example
curl http://127.0.0.1:12120/api/v1/status \
  -H "Authorization: Bearer <token>"
HTTPMeaning
200OK
202Job started (import)… poll for the result
400Bad request… body has { "error": "...", "detail": "..." }
401Missing or wrong token
403Not Ultra, or not allowed for this user
404Unknown endpoint, or the id / name wasn’t found
409basehead is busy (e.g. another import is running)
500Something failed inside basehead… body has error + detail

MCP

Streamable HTTP at http://127.0.0.1:12120/mcp, same Bearer token. Claude Code example:

claude mcp add --transport http basehead http://127.0.0.1:12120/mcp \
  --header "Authorization: Bearer <token>"
ToolSame as
statusGET status
capabilitiesGET capabilities
list_databasesGET databases
ucs_treeGET ucs
list_importsGET imports
list_import_foldersGET imports/{id}/folders
list_groupsGET groups
create_groupPOST groups
add_to_groupPOST groups/{id}/sounds
list_collectionsGET collections
get_collectionGET collections/{name}
add_to_collectionPOST collections
searchPOST search
get_soundGET sounds/{id}
get_selectionGET selection
start_importPOST import
get_import_jobGET import/{job_id}
select_in_baseheadPOST select
transferPOST transfer
xfer_to_dawPOST xfer

Connection

GET/api/v1/statusIs basehead up, what’s open
{
  "version": "2026.10.27",
  "database": "Main Library",
  "target_app": "REAPER",
  "target_running": true
}
GET/api/v1/capabilitiesWhat this install can do
{
  "api_version": 1,
  "version": "2026.10.31",
  "edition": "Ultra",
  "commands": ["GET status", "POST search", "POST collections", "..."]
}
GET/api/v1/databasesLocal + shared databases
  • Lists every local and shared database and marks the one that’s open.

Library

GET/api/v1/ucsUCS tree with file counts
  • Category → SubCategory tree, with how many files in the open database fall under each.
GET/api/v1/importsImports in the open DB
  • Name, id and root path of each Import.
GET/api/v1/imports/{id}/folders?path=Browse one level, paged
  • Child folders and file counts under path. Leave it empty for the Import root.
  • The full folder path is the folder’s id… folders aren’t stored as records.
  • Built lazily, one level at a time, so it stays fast on multi-million-file libraries.
  • limit / offset page the folders (default 100, max 1000).
Response
{ "path": "...", "total": 12, "folders": [ { "name": "Close", "path": "...", "count": 340 } ] }
GET/api/v1/groupsGroups in the open DB
  • Name and id of each Group.
POST/api/v1/groups  POST/api/v1/groups/{id}/soundsCreate a Group, add sounds
Request · POST groups
{ "name": "Car Pass-by" }
Response
{ "id": 7, "name": "Car Pass-by", "created": true }
Request · POST groups/7/sounds
{ "ids": [88123, 88130] }
Response
{ "added": 2, "skipped": 0, "missing": [] }
  • If a Group with that name already exists you get it back with "created": false.
  • Sounds already in the Group are skipped. Nothing is ever removed.
GET/api/v1/collections  GET/api/v1/collections/{name}?path=Collection tree
  • First call lists Collection names. Second returns one Collection’s folder tree with its sounds… path limits it to one subfolder.
  • Collections work across databases. Each sound returns its path and cached metadata, plus its id when it’s in the open database.
{
  "name": "Car Pass-by",
  "folders": [ { "name": "Exteriors", "folders": [...], "sounds": [...] } ],
  "sounds": [...]
}
POST/api/v1/collectionsCreate a Collection, add sounds
Request
{ "name": "Car Pass-by", "folder": "Exteriors/Close", "ids": [88123, 88130] }
Response
{
  "name": "Car Pass-by", "folder": "Exteriors/Close", "created": true,
  "added": 2, "skipped": 0, "missing": []
}
  • The Collection and every folder in folder are created if they don’t exist.
  • A Collection keeps its sounds in folders, so folder is required when you send ids.
  • Sounds already in that folder are skipped. Nothing is ever removed.

Search & sounds

GET/api/v1/sounds/{id}One file, full details
  • Everything in the sound object plus all metadata fields and markers (cue / region / loop / sync). Markers are read from WAV files.
GET/api/v1/selectionWhat the user selected
  • The files currently selected in basehead’s Results… the user picks in basehead, your tool reads them.

Actions

POST/api/v1/import  GET/api/v1/import/{job_id}Index new files
Request
{
  "paths": ["/Volumes/SFX/Deliveries/Ep01"],   // files and/or folders
  "recursive": true,                              // true only for now (false returns 400)
  "database": "Main Library",                  // optional: must be the open database (others are refused)
  "import": { "id": 42 },                         // or { "new": "Ep01 Deliveries" }
  "group":  { "new": "Car Pass-by" }             // optional, or { "id": 7 }
}
Returns right away
{ "job_id": "imp-01", "status": "running" }
Poll GET import/{job_id}
{
  "status": "done", "added": 212, "skipped": 3,
  "failed": [ { "error": "..." } ],
  "import_id": 42, "group_id": 7,
  "assets": [ { "path": "...", "id": 88123 } ]
}
  • basehead indexes files where they are… it never moves them.
  • Leave out "import" and a new Import is created, named after the folder.
  • One API import at a time… a second one returns 409 until the first is done.
POST/api/v1/selectShow files in basehead
{ "ids": [88123, 88130], "foreground": true }
  • Shows and selects those files in Results so the user can tag them with basehead’s own tools.
  • foreground (default false): true also brings basehead to the front… false leaves focus with your tool.
  • Up to 4000 ids per call.
POST/api/v1/transferCopy a sound to a folder
{
  "id": 88123,
  "dest": "/Projects/Ep01/SFX",
  "sample_rate": 48000,   // optional
  "bit_depth": 24,       // optional
  "format": "wav"        // optional: wav | flac
}
  • Uses the user’s Transfer settings (rename, handles, etc.).
  • sample_rate / bit_depth / format override for this call only… saved settings are never changed.
  • Returns the path of the written file.
  • Cloud files (CloudPacks, Sound Swap) can’t be transferred yet.
  • dest is created if it doesn’t exist.
  • One transfer at a time (409 if basehead is already transferring).
  • sample_rate: 44100, 48000, 88200, 96000, 176400 or 192000. bit_depth: 16, 24 or 32. FLAC is max 24-bit and 8 channels.
  • Refused while Force Reference is on in Transfer options, and for split (virtual) records.
POST/api/v1/xferSend sounds to your DAW’s pool / bin
Request
{ "ids": [88123, 88130] }
Response
{
  "daw": "REAPER", "sent": 2,
  "files": ["/Volumes/SFX/...wav", "..."],
  "skipped": [ { "id": 9, "reason": "offline (not found on disk)" } ]
}
  • Same as pressing X in basehead: sends the sounds to the running target DAW (Options → Target Apps): REAPER, Pro Tools, Nuendo / Cubase, Logic, Premiere or Resolve.
  • Always by reference: the DAW gets the original files and basehead makes no copy. (Pro Tools copies them into the session’s Audio Files folder itself.)
  • 409 if no target DAW is selected, it isn’t running, or basehead is busy transferring.
  • Cloud (CloudPack / Sound Swap), split and offline files are skipped with a reason. Pro Tools also skips FLAC files.
  • sent means handed to the DAW. If the DAW refuses something, basehead shows its usual message.
  • Up to 500 ids per call.

Later after v1

Already possible inside basehead, so good candidates once v1 is solid. Not promised… tell us which ones matter to you.

EndpointDoes
GET fields/{name}/valuesDistinct values for autocomplete
GET sounds/missing · duplicatesOffline media and duplicates
POST imports/{id}/rescanRescan an Import
GET targetsTarget DAWs, which is running, current project
GET daw/session-filesFiles used in the open DAW project
GET searches/savedSaved and recent searches
POST reveal · open-in-editorFinder / Explorer, wave editor
GET sounds/{id}/statsPeak, LUFS, DC offset
POST convert · sausageFormat conversion, concatenate with cue markers
POST ai/*Generate SFX, variations, transcribe (uses credits)

Not planned kept out

WhatWhy
Spot to DAWOutside Pro Tools, spot can only land at the DAW cursor… use xfer to put sounds in the DAW’s pool / bin and place them with your own tool.
Metadata writesNo DB or file metadata edits through the API for now. Use select and tag in basehead.
PlaybackThe API is headless… you get the full path and audition it yourself.
Deletes, licensing, secrets, settings writesToo risky for an outside tool.

New here?

Set it up →

basehead Local API · v1 spec · October 2026 · BaseHead, LLC. Subject to change before release.