The desktop backend exposes a local FastAPI API under /api/v1. It is intended for the bundled TuneForge frontend on the same machine and binds to 127.0.0.1 by default.
This document summarizes the current route surface. The generated OpenAPI schema in packages/shared-types/openapi.json remains the implementation source of truth for exact schemas and generated TypeScript types.
- Base path:
/api/v1 - Request and response format: JSON unless an endpoint streams a file.
- IDs: string identifiers generated by the backend.
- Timestamps: ISO 8601 strings in API responses.
- Errors use a structured
errorobject withcode,message, anddetails.
Endpoints that expose growable collections use offset pagination. Endpoint-specific follow-ups should add this contract without renaming the existing list field, so generated OpenAPI clients keep a predictable response shape.
Request query parameters:
limit- page size, default50, minimum1, maximum200.offset- zero-based row offset, default0, minimum0.
Existing search and filter parameters apply to the full matching collection before total, limit, and offset
are applied.
Response fields:
- the existing list field, such as
projectsorjobs. total- count after search and filters, before pagination.limit- the effective limit used for this response.offset- the effective offset used for this response.has_more-truewhenoffset + <returned count> < total.
Example:
{
"projects": [],
"total": 125,
"limit": 50,
"offset": 0,
"has_more": true
}Paginated endpoints must document their default sort and any supported client-selected sort keys. Sorting must be
deterministic across repeated requests: timestamp, status, search-rank, or other non-unique sort keys need a stable
tie-breaker, normally the entity's immutable ID. For example, newest-first pages should sort by
updated_at DESC, id DESC instead of timestamp alone. If a sort key can be null, the endpoint must also document
where null values appear.
This audit captures the current list-like API surface. Individual endpoint sections later in this document describe current pagination behavior or explicit unpaginated exceptions.
| Endpoint or payload | List field | Pagination decision |
|---|---|---|
GET /api/v1/jobs |
jobs |
Paginated growable list with status, project_id, and project-name search filters plus sort_by/sort_order. Default ordering is active-first and deterministic. |
GET /api/v1/projects |
projects |
Paginated growable list. search filters the full matching collection before pagination. Default ordering is updated_at DESC, id DESC. Desktop Library consumers should lazy-load this list. |
GET /api/v1/projects/{project_id}/artifacts |
artifacts |
Explicit unpaginated bounded project inventory exception: source audio, generated stems, practice mixes, exports, and cache artifacts. Ordered by created_at DESC; no pagination. |
GET /api/v1/projects/{project_id}/sections |
sections |
Explicit unpaginated project document/song-structure exception. Sections are bounded by the song arrangement and must stay complete for editing and playback. |
GET /api/v1/sync/trusted-peers |
trusted_peers |
Explicit unpaginated active manual trust-list exception. Active peers only; bounded by user-paired devices and returned complete for sync UI. Deterministic ordering is display_name case-insensitive, then device_id. |
GET /api/v1/beat-backends |
backends |
Explicit unpaginated exception: small static capability list, bounded by bundled/local backend implementations. |
GET /api/v1/chord-backends |
backends |
Explicit unpaginated exception: small static capability list, bounded by bundled/local backend implementations. |
GET /api/v1/stem-models |
models |
Explicit unpaginated exception: small static capability list, bounded by supported local stem models. |
GET /api/v1/sync/metadata |
projects, artifacts, delete_tombstones |
Explicit unpaginated sync snapshot exception. The payload is a complete sync inventory used by native sync and reconciliation, not an interactive scroll list. If scale requires chunking, it should be a sync protocol change rather than this generic pagination contract. |
GET /api/v1/sync/preflight |
projects, duplicate_groups, manual_cleanup_guidance |
Explicit unpaginated diagnostic exception. The response is a complete local-library health report. |
GET /api/v1/sync/projects/{project_id}/manifest |
entity_revisions, artifacts, delete_tombstones |
Explicit unpaginated manifest exception. The response is an atomic project export unit. |
POST /api/v1/sync/reconciliation/plan |
items, actions |
Explicit unpaginated planning exception. The response is a complete computed plan for the supplied sync inventory. |
POST /api/v1/sync/reconciliation/apply |
plan.items, plan.actions, results, timing_evidence |
Explicit unpaginated apply exception. Partial pages would make apply summaries and results incomplete. |
| Project document payloads, including analysis timing, chords, lyrics, tab imports, and tab apply results | nested content arrays and result arrays | Explicit unpaginated document exceptions unless a future endpoint exposes them as standalone growable collections. These arrays are part of a single project document or edit result, not top-level list browsing. |
Example error response:
{
"error": {
"code": "PROJECT_NOT_FOUND",
"message": "Project not found.",
"details": {}
}
}Jobs represent asynchronous work such as analysis, chords, lyrics, transforms, stems, previews, and exports.
Important fields:
idproject_idtypestatusprogresssource_artifact_idchord_backendchord_sourceerror_messageruntime_devicecreated_atupdated_at
Artifacts represent generated or imported files associated with a project.
Important fields:
idproject_idtypeformatpathsize_bytesgenerated_bycan_deletecan_regeneratemetadatacreated_at
GET /api/v1/health
Returns backend name, legacy backend git ref in version, backend/frontend package versions and git refs, status,
API base URL, data root, default export format, and preview format. Build git refs use the packaged build
metadata when available, otherwise local development resolves them with git describe --tags --long --dirty --always.
GET /api/v1/beat-backends
Returns available beat analysis backends. Built-in Beat Analysis is always expected to be available. Advanced Beat Analysis may be unavailable when optional desktop-only dependencies are not installed.
GET /api/v1/chord-backends
Returns available chord backends and capability metadata. Built-in chords are always expected to be available. Advanced Chords may be unavailable when optional desktop-only dependencies are not installed or the runtime platform is mobile.
GET /api/v1/stem-models
Returns supported stem models and availability metadata. Labels are Default (6 stems model) for htdemucs_6s and 2 stems model for htdemucs_ft.
The sync API exposes local sync metadata, identity, and trust management for the bundled desktop app. FastAPI remains bound to loopback; LAN transport, peer discovery, QR scanning, and file transfer belong to the separate native sync layer.
device_id is the stable identity for one TuneForge install. sync_group_id identifies the sync group that
trusted devices may share. Display names are editable convenience labels and must not be treated as identity.
GET /api/v1/sync/identity
Returns the local sync identity, creating it if it does not exist yet.
Response wrapper:
identity
Identity fields:
device_idsync_group_iddisplay_name- nullable convenience label.public_keycreated_at- nullable when the service returns an identity DTO without persistence metadata.updated_at- nullable when the service returns an identity DTO without persistence metadata.
PATCH /api/v1/sync/identity
Updates the local identity display name.
Request fields:
display_name
Response wrapper:
identity
Changing display_name does not change device_id, sync_group_id, or public identity material.
POST /api/v1/sync/pairing/offers
Creates a short-lived manual pairing offer for copy/paste style pairing. The endpoint does not start LAN discovery, expose FastAPI beyond loopback, or create QR UI.
Request fields:
endpoint_hints- optional advisory peer endpoints, default[].ttl_seconds- pairing offer lifetime in seconds, default600, maximum3600.
Response wrapper:
pairing_offer
Pairing offer fields:
payloadexpires_atttl_seconds
The payload is the portable value another device submits inside the payload field of
POST /api/v1/sync/trusted-peers. For this backend-first slice, a peer trust request must carry
the pairing_offer_id and pairing_secret from a pending local offer so this install can verify
the manual exchange before storing trust. It includes:
protocol_versionpairing_offer_idsync_group_iddevice_iddisplay_namepublic_keyendpoint_hintspairing_secretexpires_atsignature
Pairing payloads are signed by the source device's private identity key so copied payloads are
tamper-evident. Endpoint hints are advisory and do not authenticate a peer. The receiving install
must still explicitly trust the payload before accepting manifests, revisions, tombstones, or
artifact bytes from that device. The payload submitted to this endpoint must reference a locally
issued pairing_offer_id; that local offer must still be unused, unexpired, and match the
payload's pairing_secret.
GET /api/v1/sync/trusted-peers
Returns active peers this install explicitly trusts. Revoked peers are not returned by this list.
Response wrapper:
trusted_peers
Trusted peer fields:
device_idsync_group_iddisplay_namepublic_keyendpoint_hintstrusted_atrevoked_atupdated_at
Trust is explicit and non-transitive. Joining the same sync_group_id, discovering a device, or hearing about
a peer from another trusted device does not automatically trust that peer.
POST /api/v1/sync/trusted-peers
Stores trust for one peer from a manual pairing payload.
Request fields:
payload- pairing payload copied from another device.adopt_sync_group- whether this install should adopt the payload'ssync_group_id, defaultfalse.
Response wrapper:
trusted_peer
Trusting a peer records its device_id, public identity, display name, endpoint hints, and sync group. The
peer can later be revoked without deleting local projects or changing this install's own device_id.
Standalone payloads that do not reference a local pending offer are rejected.
PATCH /api/v1/sync/trusted-peers/{device_id}/endpoint-hints
Replaces advisory endpoint hints for an active trusted peer.
Request fields:
endpoint_hints- advisory peer endpoints. Values are trimmed and empty values are rejected.
Response wrapper:
trusted_peer
Unknown or revoked peers return 404.
DELETE /api/v1/sync/trusted-peers/{device_id}
Revokes trust for the peer with the matching device_id.
Response wrapper:
trusted_peer
Revocation stops this install from accepting future sync material from that peer unless it is explicitly paired again. Revocation is local to this install and does not revoke trust transitively on any other device.
GET /api/v1/sync/preflight
Checks the local library before multi-device sync is enabled. The endpoint returns HTTP 200 for both passing and failing preflight results because failures are actionable diagnostics rather than request errors.
The response includes:
ok- overall sync readiness. This istruewhen library checks pass. Pending or running jobs are diagnostic state, not a hard sync blocker.library_ok- library-only readiness, matching the previousokbehavior.- project status counts
- per-project sync identity status
- duplicate source-hash groups
job_state- active job diagnostics for sync startup and backend responsiveness failures.- manual cleanup guidance
job_state includes state (ready or busy), running_job_count, pending_job_count,
blocking_job_count, blocking_job_counts, blocking_jobs, blocking_jobs_truncated, and
guidance. Pending and running jobs do not block sync by themselves; they are included so the UI can
explain backend-busy or endpoint-timeout failures. blocking_jobs is capped at 20 entries and
exposes only sync-safe fields: id, project_id, project_name, type, status, progress,
started_at, and updated_at. It does not expose job payloads, file paths, audio data, or endpoint
details.
Project status values are ready, missing_source_hash, invalid_source_hash, duplicate_source_hash, and noncanonical_project_id. Canonical project IDs use proj_sha256_<full_source_sha256>, while project storage directories use a shorter derived key such as proj_<first_24_sha256_hex>. Preflight uses a stored projects.source_sha256 when present. If that field is missing, preflight only recovers it from an explicit app-managed original-byte copy and otherwise reports missing_source_hash; source_path is provenance only and is not hashed during recovery. This endpoint is sync-specific; general project responses do not expose source hashes.
GET /api/v1/sync/metadata
Returns sync-safe project and artifact metadata so sync clients do not need to read the local SQLite database directly. The endpoint does not expose absolute local file paths.
Project fields:
project_iddisplay_namesource_key_overridesource_sha256duration_secondssample_ratechannelscreated_atupdated_at
Artifact fields:
artifact_idproject_idtypeformatrelative_pathcontent_sha256size_bytesgenerated_bycan_deletecan_regeneratecache_keymetadatacreated_at
Delete tombstone fields:
tombstone_idsync_group_idproject_idtarget_typetarget_idauthor_device_iddeleted_atprior_metadatacreated_atupdated_at
relative_path is project-root relative when the artifact is stored under the backend-managed project root; otherwise it is null. Operational source_audio artifacts are app-managed WAV files under the project root. Artifact metadata is sanitized recursively before returning and removes local path-bearing keys such as source_path, original_copy_path, playback_path, imported_path, and any metadata key ending in _path. Non-path metadata such as retune/transpose settings, stem_model, and source_artifact_id is preserved. Job internals such as result_artifact_ids_json are not exposed.
POST /api/v1/sync/reconciliation/plan
Returns a stateless sync plan comparing the local library with metadata supplied by the native sync layer. The endpoint reads local state but does not write projects, artifacts, revisions, tombstones, staged files, jobs, or conflict records. Clients execute returned actions through the existing import, staging, and future sync flows.
Request fields:
remote_library- sync metadata in the same shape returned byGET /api/v1/sync/metadata, plus optionalentity_revisions:projects,artifacts,entity_revisions, and optionaldelete_tombstones.project_manifests- optional list of exported project manifests, default[].peer_inventory- peer entries withdevice_id,available_content_sha256, and optional smallmetadata.
Response fields:
summary-total_items,total_actions,total_conflicts, andstatus_counts.items- per-project, artifact, revision, or tombstone decisions withitem_type,item_id, optionalproject_id,status, optionalaction_type, optionalcontent_sha256, optionalchosen_provider_device_id, optionalreason, anddetails.actions- ordered operations withaction_type,item_type,item_id, optionalproject_id, optionalcontent_sha256, optionalprovider_device_id, optionalreason,priority, anddetails.
Planner statuses are noop, identical_content, missing_local_bytes, remote_available,
missing_provider, deleted, and conflicted.
Action types are apply_delete_tombstone, import_project_manifest, import_entity_revision,
fetch_artifact_content, import_artifact_manifest, record_conflict, and noop.
GET /api/v1/sync/projects/{project_id}/manifest
Returns a single project manifest for manifest-only sync export. The response is wrapped as project_manifest and includes:
schema_versionexported_atprojectentity_revisionsartifactsdelete_tombstones
Manifest paths are project-root relative and use portable path separators. The response does not expose absolute local source, project, artifact, or app data paths. The project entry's source_sha256 is the identity hash of the original imported source bytes. Artifact entries' content_sha256 values hash the bytes for those specific staged artifact files, including the source_audio artifact. These hashes are separate on purpose: an app-managed normalized WAV source artifact may have different bytes from the original import identity hash. sync_entity_revisions.content_sha256 is also per-entity revision payload content, not project source identity. Projects whose source_audio artifact is not an app-managed readable PCM WAV must be reimported or repaired before manifest export. Receiving peers must verify staged files against the artifact SHA-256 values and must preserve the project source_sha256 as identity metadata before accepting the import. The endpoint exports metadata only. File transfer and LAN peer discovery belong to the native sync layer, not the loopback FastAPI API.
entity_revisions carries durable sync records for project metadata, chords, lyrics, sections, regeneration events, and other editable or generated project entities. Each revision records revision_id, project_id, entity_type, entity_id, revision_type, optional base_revision_id, author_device_id, optional source_artifact_id, content_sha256, state, metadata, payload, created_at, and updated_at.
delete_tombstones carries durable group-delete records for project, artifact, and entity revision targets so offline peers do not resurrect deleted records when they reconnect. Each tombstone records the author device, delete timestamp, target type, target ID, sync group/project context, and prior metadata needed for diagnostics.
POST /api/v1/sync/projects/import
Imports a project from a previously exported project manifest plus files that have already been staged on disk by the sync transport.
Request fields:
manifeststaging_root
staging_root is a local directory containing the staged project files at the relative paths declared in the manifest. During import, the backend requires exactly one source_audio artifact, and that artifact must use format="wav" with a .wav relative path. The backend verifies source and artifact bytes with SHA-256, rewrites accepted paths into this install's backend-managed project root, and persists the project through backend services instead of copying database rows from another device. Original absolute import paths are local provenance only and are not sync-operational inputs.
If the local library already contains the same canonical project or source SHA-256, staged import rejects the duplicate with HTTP 409 instead of creating a second project. The response uses the normal project wrapper shape:
project
Staged import does not enqueue analysis, chord, lyrics, stem, or other generation jobs. Synced projects should import the durable state that was actually exported; future rebuilds are explicit user actions. Imports must reject older manifests that would recreate a project, artifact, or entity revision covered by an accepted delete tombstone. This endpoint also does not expose FastAPI on the LAN. Peer communication should keep using the separate native sync layer while FastAPI remains bound to loopback.
POST /api/v1/projects/import
Creates a project from a source path and queues initial analysis, chord, lyrics, and source stem jobs.
Request fields:
source_pathcopy_into_projectdisplay_namestem_model
Response: ProjectResponse.
stem_model is optional. Desktop imports send the user's default stem model so automatic source stems match
manual stem generation preferences; if omitted, the backend stem model default is used.
source_path records where the user imported the file from on this install. Sync treats it as local provenance, not a durable source of original bytes or an operational sync input. copy_into_project=false is accepted for compatibility, but new imports still create an app-managed operational WAV source artifact under the project root.
If the same source track has already been imported, the endpoint returns HTTP 409 with code DUPLICATE_PROJECT_SOURCE, message This project is already imported with name "{name}"., and details containing:
project_idproject_name
GET /api/v1/projects
Optional query:
search- filters projects by display name or path before pagination.limit- page size, default50, minimum1, maximum200.offset- zero-based row offset, default0, minimum0.
Default ordering is deterministic newest-first: updated_at DESC, id DESC.
Response: ProjectsResponse with projects, total, limit, offset, and has_more.
GET /api/v1/projects/{project_id}
Response: ProjectResponse.
PATCH /api/v1/projects/{project_id}
Supported fields:
display_namesource_key_override
Response: ProjectResponse.
DELETE /api/v1/projects/{project_id}
Deletes the project and project-owned data.
Response: DeleteResponse.
POST /api/v1/projects/{project_id}/analyze
Queues an analysis job.
Request fields:
include_tempoforce
Response: JobResponse.
GET /api/v1/projects/{project_id}/analysis
Returns the stored analysis result, or null if no result exists.
Analysis includes key, key confidence, reference tuning, tuning offset, tempo, analysis version, source artifact, and creation time.
Analysis is an unpaginated project document payload. Its timing data is returned with the analysis document so playback and editing consumers do not need lazy-loaded fragments.
POST /api/v1/projects/{project_id}/chords
Queues chord generation.
Request fields:
backendbackend_fallback_fromforceoverwrite_user_edits
Response: JobResponse.
GET /api/v1/projects/{project_id}/chords
Returns source segments, current timeline segments, backend, source artifact, source kind, user-edit state, metadata, and timestamps. If no chord timeline exists, the response contains an empty timeline.
Chord timelines are unpaginated project document payloads. The source and current timelines stay complete so playback and editing consumers share one coherent chord document.
POST /api/v1/projects/{project_id}/lyrics
Queues local lyrics generation.
Request fields:
forcelanguage_override(optional):nullor omitted uses Whisper auto-detection. Supported overrides arenone,en,pt,es,fr,de,it,ja,ko,zh, andhi.nonemarks the project as having no lyrics and does not run Whisper.
Response: JobResponse.
GET /api/v1/projects/{project_id}/lyrics
Returns source transcript segments, current edited segments, backend, source artifact, model/device metadata, effective language, language override, user-edit state, and timestamps. If no lyrics exist, the response contains empty segment lists.
Lyrics segments are unpaginated project document payloads. Source and edited segments stay complete so lyric editing and playback highlighting do not depend on lazy-loaded fragments.
PUT /api/v1/projects/{project_id}/lyrics
Persists edited lyric segment text.
Request fields:
segments
Each segment currently accepts:
text
Response: LyricsResponse.
GET /api/v1/projects/{project_id}/sections
Returns the complete song-structure section list for the project.
Sections are an unpaginated project document payload. They are bounded by the arrangement and stay complete so editing, playback navigation, and tab-import apply results use one shared song-structure view.
Response: SongSectionsResponse.
POST /api/v1/projects/{project_id}/retune
Queues a retune job.
Request fields:
- exactly one of
target_reference_hzortarget_cents_offset preview_onlyoutput_format
Response: JobResponse.
POST /api/v1/projects/{project_id}/transpose
Queues a transpose job.
Request fields:
semitonespreview_onlyoutput_format
Response: JobResponse.
POST /api/v1/projects/{project_id}/preview
Queues a preview job for a retune and/or transpose transform.
Request fields:
retunetransposeoutput_format
At least one transform must be provided.
Response: JobResponse.
POST /api/v1/projects/{project_id}/stems
Queues stem generation for the selected source audio or practice mix.
Request fields:
modestem_modeloutput_formatforcesource_artifact_idchord_backendchord_backend_fallback_fromoverwrite_chord_edits
Current validation allows mode: "stems" or mode: "two_stem" and output_format: "wav". If stem_model is omitted, mode: "stems" uses the backend default htdemucs_6s; mode: "two_stem" maps to htdemucs_ft for compatibility.
htdemucs_6s creates visible Vocals, Drums, Bass, Guitar, Piano, and Other artifacts. htdemucs_ft creates visible Vocals and Instrumental artifacts.
Response: JobResponse.
GET /api/v1/projects/{project_id}/artifacts
Returns the bounded project inventory for source audio, generated stems, practice mixes, exports, and cache artifacts.
Results are ordered by created_at DESC.
Project artifacts are not paginated. They are project-scoped inventory, not a library-level growable browsing list.
Response: ArtifactsResponse.
DELETE /api/v1/projects/{project_id}/artifacts/{artifact_id}
Deletes a project artifact when it belongs to the project and can be removed.
Response: DeleteResponse.
POST /api/v1/projects/{project_id}/export
Queues an export job for selected project artifacts.
Request fields:
artifact_idsmixdown_modeoutput_formatdestination_path
At least one artifact ID is required.
Response: JobResponse.
GET /api/v1/jobs
Returns a paginated list of jobs.
Query parameters:
limit- page size, default50, minimum1, maximum200.offset- zero-based row offset, default0, minimum0.status- optional repeatable status filter, for example?status=pending&status=running.project_id- optional project ID filter.search- optional project display name filter. Search is applied before pagination and composes withstatusandproject_idfilters.sort_by- optional sort key. Supported values areactivity(default),created_at,started_at,updated_at, andstatus.sort_order- optionalascordescdirection for non-activity sorts. Omit this whensort_byisactivityor omitted;sort_orderwith activity sorting returns422 INVALID_REQUEST.
Filters and search are applied before sorting, total, and pagination. Pagination is applied after sorting.
Default activity ordering:
- running jobs first, ordered by
started_atwhen present, otherwisecreated_at, ascending, thenidascending. - pending jobs next, ordered by
created_atascending, thenidascending. - terminal jobs (
completed,cancelled,failed) next, ordered bycompleted_atwhen present, otherwiseupdated_at, descending, theniddescending. - unknown statuses last, ordered by
updated_atdescending, theniddescending.
Timestamp ordering:
created_at,started_at, andupdated_atdefault todesc; usesort_order=ascfor oldest-first.nulltimestamp values are always last for bothascanddesc.- Timestamp ties use
idascending as the deterministic tie-breaker.
Status ordering:
sort_by=statusdefaults toasc.ascgroups jobs asrunning,pending,completed,cancelled,failed, then unknown statuses.descreverses those groups.- Jobs inside each status group use the same deterministic tie-breakers as activity ordering for that group.
Response: JobsResponse.
GET /api/v1/jobs/{job_id}
Response: JobResponse.
POST /api/v1/jobs/{job_id}/cancel
Requests cancellation for a job.
Response: JobResponse.
GET /api/v1/artifacts/{artifact_id}/stream
Streams the artifact file as an audio response. Returns ARTIFACT_NOT_FOUND if the artifact row or file is missing.