API가 하는 일
Jam Jam REST API의 /mastering 계열은 프리셋으로 오디오 파일을 렌더링해요. 음색 균형을 맞추고, 치찰음을 누그러뜨리고, 스테레오 폭을 정하고, 레벨을 음량 목표(스트리밍, Apple Music, Deezer, 팟캐스트, 클럽)에 맞춰요. 설정은 jam-jam.org/app/mastering의 웹 도구 설정에 대응해요. OpenAPI 3.1 명세는 jam-jam.org/api/v1/openapi.json에, 레퍼런스는 jam-jam.org/developers에 있어요. 파일은 최대 200 MB, 10분이고, 모노 또는 스테레오예요(2채널을 넘으면 앞의 2채널을 써요). 처리는 48 kHz로 이루어져요. 출력은 16비트(wav-pcm16, 기본값) 또는 32비트 부동소수점(wav-f32) WAV이고, API는 24비트를 제공하지 않아요. 원본 파일은 디코딩 즉시 삭제되고 결과는 24시간 보관돼요. 서버 렌더링은 재현할 수 있지만(같은 파일, 같은 설정, 같은 engineVersion), 브라우저 내보내기와 비트 단위로 같다고 보장하지는 않아요.
키 받기와 접근 권한 확인하기
jam-jam.org에서 계정을 만들고 설정, API 키(jam-jam.org/app/prefs/api-keys) 순서로 열어 mastering:read와 mastering:write 범위를 가진 키를 만드세요. 키는 jj_live_로 시작하고, 한 번만 표시되며, 계정당 5개까지 가질 수 있어요. 키는 환경 변수 JAM_JAM_API_KEY에 보관하고, 저장소나 공유 명령줄에는 절대 두지 마세요. API로 파일을 렌더링하는 기능(POST /mastering/jobs)은 모든 계정에서 쓸 수 있어요. 무료 계정은 ISO 주마다 3회(웹 도구와 같은 카운터), Premium은 상한이 없어요. 명세 읽기와 프리셋 목록 보기·저장도 모든 계정에서 쓸 수 있어요.
최신 명세 읽기
GET /mastering/capabilities는 키가 필요 없어요. 엔진 버전, 각 설정의 범위와 단계, 음량 목표, 내장 프리셋, 제한을 돌려주고, 키를 쓰면 renderAllowed와 카운터 잔량도 함께 줘요. 한계값을 코드에 고정하지 말고 이것을 읽으세요. 내장 프리셋 5개는 읽기 전용이고 preset=builtin:<id>로 써요. builtin:balanced, builtin:warm, builtin:bright, builtin:punchy, builtin:crushed예요. POST /mastering/presets로 내 프리셋을 최대 50개(이름은 60자 이내) 저장하고 ID로 불러올 수도 있어요.
명세 읽기(키 불필요)
curl https://jam-jam.org/api/v1/mastering/capabilities curl로 파일 렌더링하기
흐름은 Jam Jam의 모든 작업과 같아요. 202로 응답하는 POST, 끝날 때까지 폴링, 그리고 내려받기예요. Idempotency-Key 헤더(A-Z, a-z, 0-9, _, -로 이루어진 16~80자)는 모든 쓰기에서 필수이고, 같은 키와 같은 본문을 다시 보내면 두 번 과금되지 않고 같은 작업이 돌아와요. 작업은 queued, decoding, analyzing, rendering을 거쳐 done, failed, cancelled, expired 중 하나로 끝나고, 완료되면 렌더링 전후의 통합 음량(LUFS)과 트루 피크(dBTP)가 담긴 report가 붙어요. 계정당 진행 중인 렌더링은 1개, 대기는 최대 3개예요. 예제 저장소에는 같은 단계가 번호가 붙은 스크립트(curl/09-mastering-capabilities.sh부터 curl/14-mastering-delete.sh까지)로 들어 있어요.
내장 프리셋으로 렌더링하고, 작업을 따라가서, WAV 내려받기
# 1. Upload the file with a built-in preset (Idempotency-Key is required)
curl -X POST https://jam-jam.org/api/v1/mastering/jobs \
-H "Authorization: Bearer $JAM_JAM_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F [email protected] -F preset=builtin:warm -F format=wav-pcm16
# The 202 answer carries the job "id": JOB_ID=<that id>
# 2. Follow the job, honouring Retry-After, until "status" is "done"
curl https://jam-jam.org/api/v1/mastering/jobs/$JOB_ID \
-H "Authorization: Bearer $JAM_JAM_API_KEY"
# 3. Download the mastered WAV
curl -o mix-master.wav https://jam-jam.org/api/v1/mastering/jobs/$JOB_ID/result \
-H "Authorization: Bearer $JAM_JAM_API_KEY" Python과 Node
이 두 코드는 예제 저장소 github.com/titouannwtt/jam-jam-api-examples(MIT 라이선스)의 python/mastering_lib.py와 javascript/mastering-lib.mjs를 줄인 것이에요. Python은 3.10 이상과 requests, Node는 20 이상이고 의존성이 없어요. 전체 스크립트는 HTTP 코드마다 읽기 쉬운 메시지를 더하고 python mastering.py master mix.wav --preset builtin:warm 또는 node mastering.mjs master mix.wav --preset builtin:warm으로 실행해요.
Python(3.10 이상, requests)
import os, time, uuid, requests
API = "https://jam-jam.org/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['JAM_JAM_API_KEY']}"}
FINAL = ("done", "failed", "cancelled", "expired")
# 1. Upload the file with a built-in preset. Idempotency-Key is required.
with open("mix.wav", "rb") as audio:
res = requests.post(
f"{API}/mastering/jobs",
headers={**HEADERS, "Idempotency-Key": str(uuid.uuid4())},
data={"preset": "builtin:warm", "format": "wav-pcm16"},
files={"file": ("mix.wav", audio)},
)
res.raise_for_status()
job = res.json()
# 2. Poll, waiting as long as the server asks.
while job["status"] not in FINAL:
time.sleep(int(res.headers.get("Retry-After") or 2))
res = requests.get(f"{API}/mastering/jobs/{job['id']}", headers=HEADERS)
res.raise_for_status()
job = res.json()
if job["status"] != "done":
raise SystemExit(f"mastering {job['status']}: {job.get('error')}")
before, after = job["report"]["before"], job["report"]["after"]
print(f"LUFS {before['integratedLufs']} -> {after['integratedLufs']}")
# 3. Download the mastered WAV.
audio = requests.get(f"{API}/mastering/jobs/{job['id']}/result", headers=HEADERS)
audio.raise_for_status()
with open("mix-master.wav", "wb") as out:
out.write(audio.content) Node(20 이상, 의존성 없음)
import { openAsBlob } from 'node:fs';
import { writeFile } from 'node:fs/promises';
const API = 'https://jam-jam.org/api/v1';
const headers = { Authorization: `Bearer ${process.env.JAM_JAM_API_KEY}` };
const FINAL = ['done', 'failed', 'cancelled', 'expired'];
// 1. Upload the file with a built-in preset. Idempotency-Key is required.
const body = new FormData();
body.append('file', await openAsBlob('mix.wav'), 'mix.wav');
body.append('preset', 'builtin:warm');
body.append('format', 'wav-pcm16');
let res = await fetch(`${API}/mastering/jobs`, {
method: 'POST',
headers: { ...headers, 'Idempotency-Key': crypto.randomUUID() },
body,
});
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
let job = await res.json();
// 2. Poll, waiting as long as the server asks.
while (!FINAL.includes(job.status)) {
await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 2) * 1000));
res = await fetch(`${API}/mastering/jobs/${job.id}`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
job = await res.json();
}
if (job.status !== 'done') throw new Error(`mastering ${job.status}: ${JSON.stringify(job.error)}`);
const { before, after } = job.report;
console.log(`LUFS ${before.integratedLufs} -> ${after.integratedLufs}`);
// 3. Download the mastered WAV.
res = await fetch(`${API}/mastering/jobs/${job.id}/result`, { headers });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
await writeFile('mix-master.wav', Buffer.from(await res.arrayBuffer())); 내 설정 쓰기
프리셋 대신 설정을 JSON으로 settings 필드에 넣어 보내세요. preset과 settings 중 정확히 하나만 지정해요. 형식(MasteringSettingsV1)은 value와 enabled 플래그를 가진 8개 모듈, 음량 목표(streaming, apple, deezer, podcast, club), normalize로 이루어져요. 음색 설정은 데시벨, 나머지는 퍼센트이고 최대 150이에요. 범위나 단계에서 벗어난 값은 문제마다 항목 하나가 붙은 422 invalid-settings로 응답해요. 설정을 재사용하려면 POST /mastering/presets로 저장하고 그 ID를 preset에 넘기세요. 설정은 업로드할 때 복사되므로, 나중에 프리셋을 바꿔도 이미 시작한 작업에는 영향이 없어요.
내 설정 보내기
{
"modules": {
"autoEq": { "value": 50, "enabled": true },
"lows": { "value": 2, "enabled": true },
"mids": { "value": -0.5, "enabled": true },
"highs": { "value": -1.5, "enabled": true },
"deEss": { "value": 40, "enabled": true },
"width": { "value": 10, "enabled": true },
"loudness": { "value": 45, "enabled": true },
"destruct": { "value": 0, "enabled": false }
},
"target": "streaming",
"normalize": false
} 내 설정 보내기
curl -X POST https://jam-jam.org/api/v1/mastering/jobs \
-H "Authorization: Bearer $JAM_JAM_API_KEY" -H "Idempotency-Key: $(uuidgen)" \
-F [email protected] -F "settings=<samples/mastering-settings.json" -F format=wav-pcm16 폴더 전체를 일괄 처리하기
저장소에는 폴더를 프리셋 하나로 파일 하나씩 마스터링하는 일괄 처리 스크립트 python/mastering_batch.py와 javascript/mastering-batch.mjs가 들어 있어요. 따라 할 만한 선택이 세 가지 있어요. 계정이 진행 중 렌더링 1개와 대기 3개만 받으므로 파일은 하나씩 보내요. 이름-master.wav가 이미 있는 파일은 건너뛰어서 중단 후 다시 실행할 수 있어요. 대기 시간이 짧은 429(최대 5분)에서는 Retry-After만큼 기다렸다가 같은 파일을 같은 멱등성 키로 다시 보내고, 403이나 긴 429에서는 멈춰요. 다음 파일도 똑같이 실패할 테니까요.
폴더를 파일 하나씩 마스터링하기
python mastering_batch.py ~/Music/mixes --preset builtin:balanced --out masters
node mastering-batch.mjs ~/Music/mixes --preset builtin:balanced --out masters 오류와 할당량
모든 오류는 확인할 수 있는 안정적인 code를 가진 application/problem+json이고, 렌더링 응답에는 X-Mastering-Quota-Limit, X-Mastering-Quota-Remaining, X-Mastering-Quota-Reset 헤더가 붙어요. Premium 계정에는 렌더링 횟수 상한이 없어요. 무료 계정에서 코드가 quota-exhausted인 429는 그 주의 렌더링 횟수를 다 썼다는 뜻이고, 그 밖의 429는 속도 제한이나 대기열 혼잡을 뜻해요. 실패하거나 취소된 렌더링은 세지 않아요. API로 파일을 렌더링하는 기능(POST /mastering/jobs)은 모든 계정에서 쓸 수 있어요. 무료 계정은 ISO 주마다 3회(웹 도구와 같은 카운터), Premium은 상한이 없어요. 명세 읽기와 프리셋 목록 보기·저장도 모든 계정에서 쓸 수 있어요.
| 상태 | 코드 | 의미 |
|---|---|---|
| 401 | 키가 없거나 올바르지 않아요. | |
| 403 | insufficient-scope | 키에 mastering:read 또는 mastering:write 범위가 없어요. |
| 403 | premium-required | 현재 요금제에서는 렌더링을 쓸 수 없어요(GET /mastering/capabilities의 renderAllowed 확인). |
| 409 | not-ready | 렌더링이 끝나기 전에 결과를 요청했어요. |
| 410 | expired | 24시간이 지났어요. 다시 렌더링하세요. |
| 413, 415, 422 | 파일이 너무 크거나, 형식이 지원되지 않거나, 오디오가 10분을 넘거나, 설정이 올바르지 않아요(errors[] 확인). | |
| 429 | rate-limited | 요청이 너무 많거나, 렌더링 1건이 실행 중이고 3건이 대기 중이거나(client-quota), 대기열이 가득 찼어요(queue-full): Retry-After초만큼 기다리고 반복하지 마세요. |
어시스턴트용 MCP 서버
공식 MCP 서버 @jam-jam/convert-mcp는 Claude Desktop, Claude Code, Cursor 같은 Model Context Protocol 클라이언트에 마스터링을 제공하고, 키의 범위가 허용하는 도구만 보여 줘요. 마스터링 도구는 6개예요. jj_mastering_capabilities(범위 없음), jj_list_mastering_presets, jj_create_mastering_preset, jj_master_audio, jj_mastering_get_job, jj_mastering_cancel_job이에요. jj_master_audio는 로컬 파일을 프리셋 또는 인라인 설정으로 업로드하고, maxWaitMs를 쓰면 결과를 기다렸다가 내려받아요. 그다음 어시스턴트에게 warm 프리셋으로 파일을 마스터링하고 전후 음량을 알려 달라고 부탁할 수 있어요. 키는 환경에 두고 명령줄에는 절대 쓰지 마세요.
MCP 클라이언트 설정
{
"mcpServers": {
"jam-jam": {
"command": "npx",
"args": ["-y", "@jam-jam/convert-mcp"],
"env": { "JAM_JAM_API_KEY": "jj_live_..." }
}
}
} 설정이 무엇을 하는지, 어느 정도 음량을 목표로 할지 알고 싶다면 곡을 무료로 마스터링하는 방법 가이드를 읽어 보세요.
마스터링 API 자주 묻는 질문
Jam Jam의 마스터링 API는 무료인가요?
네. 무료 계정은 POST /mastering/jobs로 ISO 주마다 파일 3개를 렌더링할 수 있어요(웹 도구와 같은 카운터). 실패하거나 취소된 렌더링은 돌려받아요. Premium은 상한이 없어요. 최신 명세 읽기와 프리셋 관리는 무료예요.
파일 제한은 어떻게 되나요?
최대 200 MB, 10분이고 모노 또는 스테레오예요. 결과는 48 kHz의 16비트, 24비트 또는 32비트 부동소수점 WAV이고 24시간 보관돼요.
결과가 브라우저와 똑같나요?
서버에서는 재현할 수 있지만(같은 파일, 같은 설정, 같은 engineVersion), 브라우저 내보내기와 비트 단위로 같다고 보장하지는 않아요. 설정은 같아요.
여러 파일을 한꺼번에 보낼 수 있나요?
계정당 진행 중인 렌더링은 1개, 대기는 최대 3개예요. 폴더는 예제 저장소의 일괄 처리 스크립트로 파일을 하나씩 보내세요.
AI 어시스턴트에서는 어떻게 쓰나요?
MCP 서버 @jam-jam/convert-mcp를 쓰세요. jj_master_audio 도구가 로컬 파일을 마스터링하고 jj_mastering_get_job이 전후 음량을 읽어요.
같은 엔진을 브라우저에서 무료로 써 볼 수 있어요. 파일은 기기를 떠나지 않아요.