Trace
Results & Billing
Poll any Trace job until it completes, read its scores, and fetch results too large to return inline. This page also covers Trace errors, what is billed, budget caps, safe retries and rate limits.
- Job status
- Polling
- Reading the results
- Multiple layers
- Large results
- Errors
- Billing
- Budget cap
- Safe retries
- Rate limits
Job status
GET/trace/v3/jobs/{job_id}status and results of any Trace job
Works for every Trace job: custom search, comprehensive search, use case and ingestion. Polling is free.
200 OK
{
"success": true,
"data": {
"job_id": "018f0000-0000-7000-8000-0000000000a2",
"status": "completed",
"error": null,
"results": { ... }
}
}| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| data.status | string | No | — | pending (queued), processing, completed or failed. |
| data.error | string | null | No | — | Why the job failed, when status is failed. Failed jobs are not charged. |
| data.results | object | No | — | The scores. Present once the job has completed. |
| data.results_complete | boolean | No | — | Present and false when the result was too large to include. See Large results. |
| data.results_url | string | No | — | Present with results_complete: the path to fetch the full result from. |
A job that does not exist, or that belongs to another account, returns 404 with "job not found". The two cases are deliberately indistinguishable.
Polling
Most searches finish within seconds to a few minutes. A 1:1 search over many layers takes longest, because both songs are analysed on the spot. Poll every 5 to 15 seconds.
import time
import requests
API = "https://apiv2.soundverse.ai"
HEADERS = {"Authorization": "Bearer sksoundverse_..."}
def wait_for_job(job_id: str, interval: float = 10.0) -> dict:
while True:
data = requests.get(f"{API}/trace/v3/jobs/{job_id}", headers=HEADERS).json()["data"]
if data["status"] == "completed":
if data.get("results_complete") is False:
return requests.get(API + data["results_url"], headers=HEADERS).json()
return data["results"]
if data["status"] == "failed":
raise RuntimeError(data["error"])
time.sleep(interval)Reading the results
Every layer reports three scores from 0 (no similarity) to 1 (identical): hungarian_mean (the headline score), max_then_mean and max. See Reading scores. percentage is the ranking score multiplied by 100.
Results can include more fields than shown here, such as per-window detail (score_matrix, matched_windows). Read the fields you need and ignore the rest; fields may be added over time.
1:1 result
{
"status": "success",
"search_type": "1:1",
"depth": "track",
"full_audio_latent_scores": {
"hungarian_mean": 0.576373,
"max_then_mean": 0.636297,
"max": 0.725281,
"matched_windows": [
{
"query_index": 3,
"candidate_index": 5,
"similarity_score": 0.725281,
"query_audio_timestamps": [24.0, 32.0],
"candidate_audio_timestamps": [40.0, 48.0]
}
],
"score_matrix": [[0.51, 0.48, ...], ...]
}
}matched_windows shows which moments of the two songs line up, as start and end times in seconds.
1:N result
matches lists every song in the dataset, most similar first:
{
"status": "success",
"search_type": "1:n",
"depth": "track",
"candidate_dataset_id": "018f0000-0000-7000-8000-0000000000d1",
"matches": [
{
"id": "d79efad889c32e5537210f024e073632ec65fb9b5feb8576e91cad7c2bdc0466.mp3",
"score": 1.0,
"percentage": 100.0,
"full_audio_latent_scores": { "hungarian_mean": 1.0, "max_then_mean": 1.0, "max": 1.0 }
},
{
"id": "b3ea82b7b97c8e2f733cd7850d12db41f4f334aa59c88cdd09538c3554d28326.mp3",
"score": 0.636743,
"percentage": 63.67,
"full_audio_latent_scores": { "hungarian_mean": 0.636743, "max_then_mean": 0.69, "max": 0.81 }
}
]
}Mapping a match to your file
Each match’s id is Trace’s audio ID. Find it in the dataset’s tracks list: the track with that audio_id carries your file_id as source_ref.
M:N result
results holds one entry per query song, each with its own matches:
{
"search_type": "m:n",
"depth": "track",
"query_dataset_id": "018f0000-0000-7000-8000-0000000000d2",
"candidate_dataset_id": "018f0000-0000-7000-8000-0000000000d1",
"results": [
{
"query_audio_id": "e4a4d5f567804d885eb41b56ad039079049f9f11a10ae743f397a42504ad6bea.mp3",
"matches": [
{ "id": "b3ea82b7b97c8e2f733cd7850d12db41f4f334aa59c88cdd09538c3554d28326.mp3", "score": 0.71, "percentage": 71.0 },
{ "id": "d79efad889c32e5537210f024e073632ec65fb9b5feb8576e91cad7c2bdc0466.mp3", "score": 0.62, "percentage": 62.0 }
]
}
]
}Multiple layers
When a search covers several layers, each layer adds its own block next to the others:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| track | layer | No | — | full_audio_latent_scores |
| light_stem | layer | No | — | vocal_latent_scores and instrumental_latent_scores |
| stem | layer | No | — | Per-instrument scores. A 1:n search with only stem groups its matches by instrument in matches_by_stem. |
| section | layer | No | — | section_breakdown |
| motif | layer | No | — | motif_matches |
| lyrics / translation | layer | No | — | lyrics_similarity and translation_similarity |
| insights | layer | No | — | tag_overlap |
Matches are ranked by the first available layer in this order: track, light_stem, stem, section, stem_section, motif.
Large results
GET/trace/v3/jobs/{job_id}/resultsthe complete result
When a result is larger than about 200 KB, which is common for m:n searches over several layers, the job response does not include it:
{
"success": true,
"data": {
"job_id": "018f0000-0000-7000-8000-0000000000a4",
"status": "completed",
"error": null,
"results": {},
"results_complete": false,
"results_url": "/trace/v3/jobs/018f0000-0000-7000-8000-0000000000a4/results"
}
}Fetch results_url. It returns the results object directly, without the success/data envelope, in the same shape as data.results above. It also works for small results.
Download large results within 24 hours
The stored copy of a large result is available for 24 hours after the job completes. After that the endpoint returns 404 and you need to run the search again.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| job not found | 404 | No | — | The job does not exist or belongs to another account. |
| this job has no results yet | 404 | No | — | The job is still running, or it failed. Check its status. |
| results unavailable -- the link has expired; … | 404 | No | — | More than 24 hours have passed since a large result was produced. Run the search again. |
Errors
Trace uses the same error responses as the rest of the API — see Errors. Validation errors name the field in message and are listed on Search and Datasets & Ingest. Trace adds a few of its own:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| 404 | ResourceNotFound | No | — | A job or dataset does not exist or belongs to another account. Trace never answers 403, so it never confirms another account's IDs. |
| 409 | detail | No | — | A dataset name is already taken, or an ingestion is still running on a dataset you are deleting. Dataset endpoints return the Trace service's own detail message. |
| 400 | legacy upload | No | — | POST /trace/v3/upload received no file or an unsupported format. |
| 502 | service unreachable | No | — | The Trace service was briefly unreachable on a dataset, library or upload call. Retry with backoff. |
Billing
Trace bills your balance per job, based on usage.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| Billed | per job | No | — | Ingestion, custom search, comprehensive search and every use case. |
| Free | no charge | No | — | Uploads, creating, listing, viewing and deleting datasets, the library, job status and full results. |
- What a job costs depends on the songs’ length, the layers involved and, for searches, the number of comparisons.
- When you are charged: a hold may be placed when a job is queued, and the final amount settles after the job completes, usually within a minute or two.
- Failed jobs are not charged, and any hold is returned in full. Requests rejected with a
4xxnever create a job.
Check your balance with GET /v1/account/balance.
Budget cap
Add budget_tokens to any ingest or search request to cap what the job may cost. Trace prices the job before doing any work. If the price is more than 10% above your cap, the job is refused and finishes as failed, and you are not charged. The error states the job’s price:
This job prices at 77 tokens but was reserved for only 1. Re-submit so the correct amount is held; running now would spend more than was checked against the balance.Re-submit with a budget_tokens at least as high as the quoted price, or leave it out to run without a cap.
Safe retries
Send an Idempotency-Key header on every request that creates a Trace job. Repeating a request with the same key within 24 hours returns the same job instead of creating, and charging for, a second one. The key rules are on Idempotency-Key.
Idempotency-Key: catalog-check-2026-10-22-0001Rate limits
Job-creating requests are rate-limited per account over hourly and daily windows. Each kind of job (ingestion, custom search, comprehensive search, use case) has its own limit, and all Trace jobs also share an overall limit; the eight use cases count together as one kind. Limits depend on your account. Status checks, results, dataset management and uploads do not count towards them.
Exceeding a limit returns 429 RateLimited; wait, then retry with backoff and the same Idempotency-Key. See Rate limits.