Upload Once, Process Many
Tutorial · 5 min · cURL + Python
The problem
Running multiple analyses on the same file — BPM, key detection, vocal separation, transcription — used to mean uploading the audio bytes once per endpoint call. That wastes bandwidth, adds latency per call, and is frustrating when working with large files on slow connections.
The Assets API solves this: upload once, reference by asset_id for
every subsequent call.
Step 1: Upload the audio
POST raw audio bytes to /v1/assets/upload. This costs zero credits
and returns an asset_id. The same SHA-256 hash always returns the same
ID, so it’s safe to call this unconditionally on every run — it won’t
create duplicates.
curl -X POST https://api.brizm.dev/v1/assets/upload \
-H "Authorization: Bearer tl_live_xxx" \
-H "Content-Type: audio/mpeg" \
--data-binary @track.mp3
{
"asset_id": "afa_abc123",
"sha256": "e3b0c44298fc1c149afb4c8996fb924...",
"size_bytes": 8421376,
"expires_at": "2026-05-22T10:00:00Z"
}
Step 2: Run multiple analyses
Pass Content-Type: application/json and {"asset_id": "afa_abc123"}
to any processing endpoint. Credit cost is identical to a direct upload — you’re
paying for compute, not bandwidth.
# BPM detection
curl -X POST https://api.brizm.dev/v1/bpm \
-H "Authorization: Bearer tl_live_xxx" \
-H "Content-Type: application/json" \
-d '{"asset_id": "afa_abc123"}'
# Key detection — same asset, no re-upload
curl -X POST https://api.brizm.dev/v1/key \
-H "Authorization: Bearer tl_live_xxx" \
-H "Content-Type: application/json" \
-d '{"asset_id": "afa_abc123"}'
# LUFS / loudness
curl -X POST https://api.brizm.dev/v1/lufs \
-H "Authorization: Bearer tl_live_xxx" \
-H "Content-Type: application/json" \
-d '{"asset_id": "afa_abc123"}'
# List your stored assets
curl https://api.brizm.dev/v1/assets \
-H "Authorization: Bearer tl_live_xxx"
Python workflow
A realistic pattern: upload once, fan out to multiple analyses concurrently, collect all results before the asset expires.
import os, concurrent.futures, requests
API = "https://api.brizm.dev/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['TL_API_KEY']}"}
def upload_asset(path: str) -> str:
"""Upload audio bytes. Returns asset_id (0 credits charged)."""
with open(path, "rb") as f:
data = f.read()
content_type = "audio/mpeg" if path.endswith(".mp3") else "audio/wav"
r = requests.post(
f"{API}/assets/upload",
headers={**HEADERS, "Content-Type": content_type},
data=data,
)
r.raise_for_status()
return r.json()["asset_id"]
def analyze_endpoint(asset_id: str, endpoint: str) -> dict:
"""POST asset_id to a processing endpoint."""
r = requests.post(
f"{API}/{endpoint}",
headers={**HEADERS, "Content-Type": "application/json"},
json={"asset_id": asset_id},
)
r.raise_for_status()
return r.json()
# 1. Upload once
asset_id = upload_asset("track.mp3")
print(f"Asset: {asset_id}")
# 2. Fan out to multiple endpoints concurrently (same asset, no re-upload)
endpoints = ["bpm", "key", "lufs", "chords"]
with concurrent.futures.ThreadPoolExecutor() as pool:
futures = {pool.submit(analyze_endpoint, asset_id, ep): ep for ep in endpoints}
results = {}
for future in concurrent.futures.as_completed(futures):
ep = futures[future]
results[ep] = future.result()
print(f" {ep}: done")
# 3. Use the results
print(f"BPM : {results['bpm']['tempo']}")
print(f"Key : {results['key']['key']} {results['key']['mode']}")
print(f"LUFS : {results['lufs']['integrated_lufs']} dBFS")
print(f"Chords: {len(results['chords']['chords'])} detected")
Calling upload_asset() on the same file twice returns the same asset_id. Your code can safely call it unconditionally — there's no cost and no duplicate entry is created.
Step 3: Check remaining assets
Assets auto-expire (24 h on BASIC, 7 d on PRO). List your current assets anytime to audit what’s stored.
curl https://api.brizm.dev/v1/assets \
-H "Authorization: Bearer tl_live_xxx"
{
"items": [
{
"asset_id": "afa_abc123",
"sha256": "e3b0c44298fc1c149afb4c8996fb924...",
"size_bytes": 8421376,
"content_type": "audio/mpeg",
"created_at": "2026-05-15T10:00:00Z",
"expires_at": "2026-05-22T10:00:00Z"
}
],
"next_cursor": null,
"count": 1
}
Supported endpoints
All POST processing endpoints accept {"asset_id": "afa_xxx"} as an
alternative to raw audio bytes:
| Endpoint | What it computes | Credits |
|---|---|---|
POST /v1/bpm | Tempo + confidence | 2 |
POST /v1/key | Musical key + mode | 2 |
POST /v1/lufs | Integrated loudness | 2 |
POST /v1/key-bpm | Key + BPM together | 2 |
POST /v1/beatgrid | Frame-level beat timestamps | 4 |
POST /v1/chords | Chord timeline | 4 |
POST /v1/pitch | Pitch detection | 2 |
POST /v1/master | Auto-mastering (async) | 5 × minutes |
POST /v1/separate/2stem | Vocal / instrumental | 6 × minutes |
POST /v1/separate/4stem | Drums / bass / vocal / other | 10 × minutes |
POST /v1/transcribe | Audio-to-MIDI | 10 × minutes |
POST /v1/segment-transcribe | Segment + transcribe | 12 × minutes |
POST /v1/analyze/upload | Full analysis (async) | 10 |
POST /v1/identify | Track fingerprinting | 3 |
For long-running operations like stem separation or transcription, passing an asset_id works exactly the same — the endpoint returns 202 with a job_id. Use the usual polling or webhook flow to retrieve results.
Next steps
- Assets reference — full endpoint specs, TTL details, pagination.
- Async Jobs & Webhooks — polling and webhook delivery for long-running operations.
- Credits & Rate Limits — understand credit costs per endpoint.