NomonoPlatform docs

Getting started

Quickstart

Upload a WAV file of one person talking, run a job that enhances the voice, and download the result. Every step has a curl and a Python version, and the Python steps add up to the script at the end.

Before you begin

You need:

  • An API key with the jobs:write and outputs:read scopes, as described in Authentication.
  • A WAV file of one person talking, at least one second long, saved as host.wav in the current directory.

Run the curl steps in order in one shell, with curl 7.84 or later and jq installed. The Python steps need Python 3.9 or later and the requests package. Start by setting your key:

export NOMONO_API_KEY="nm_sk_..."

Upload the file

Uploading a file takes three requests.

  1. Create an upload

    Send the file name and its size in bytes, and the response gives you the upload_id, the part_size and a URL for each part.

    curl -sS https://platform-api.nomono.co/v1/uploads \  -H "Authorization: Bearer $NOMONO_API_KEY" \  -H "Content-Type: application/json" \  -o upload.json \  -d @- <<EOF{"filename": "host.wav", "size_bytes": $(wc -c < host.wav)}EOFUPLOAD_ID=$(jq -r .upload_id upload.json)

    For a file of 176,084,204 bytes, the response is:

    201 Created
    {  "upload_id": "upl_01a1446f235c7448abbe096bfd043ae3",  "part_size": 67108864,  "parts": [    {"part_number": 1, "url": "https://..."},    {"part_number": 2, "url": "https://..."},    {"part_number": 3, "url": "https://..."}  ],  "expires_at": "2026-10-17T09:02:11.204518Z"}
  2. Send the parts

    Every part is part_size bytes, 64 MiB, except the last. PUT each part to its URL without the API key, and keep the ETag header from each response.

    split -b "$(jq .part_size upload.json)" host.wav host.wav.part.PARTS="[]"PART_NUMBER=1for PART_FILE in host.wav.part.*; do  URL=$(jq -r ".parts[$PART_NUMBER - 1].url" upload.json)  ETAG=$(curl -sS --fail -T "$PART_FILE" -o /dev/null -w "%header{etag}" "$URL")  PARTS=$(jq -c --argjson part_number "$PART_NUMBER" --arg etag "$ETAG" \    '. + [{part_number: $part_number, etag: $etag}]' <<< "$PARTS")  PART_NUMBER=$((PART_NUMBER + 1))donerm host.wav.part.*
  3. Complete the upload

    Send each part number with its ETag, and the API checks the file and returns the source:

    curl -sS "https://platform-api.nomono.co/v1/uploads/$UPLOAD_ID/complete" \  -H "Authorization: Bearer $NOMONO_API_KEY" \  -H "Content-Type: application/json" \  -o source.json \  -d @- <<EOF{"parts": $PARTS}EOFSOURCE_ID=$(jq -r .source_id source.json)
    201 Created
    {  "source_id": "src_01a1446f235c7448abbe096c9b9adf6c",  "upload_id": "upl_01a1446f235c7448abbe096bfd043ae3",  "filename": "host.wav",  "format": "wav",  "channels": 1,  "sample_rate": 48000,  "duration_s": 1834.21,  "size_bytes": 176084204,  "device": null,  "status": "ready",  "created_at": "2026-10-16T09:02:11.204518Z",  "expires_at": "2026-10-23T09:04:37.880912Z"}

The upload and its URLs expire after 24 hours. If you need new URLs before then, see Uploads.

Run a job

  1. Create a session

    A session groups the sources of one recording and gives each one a role. Here the only source is a microphone labelled Host, and the label is used to name its outputs.

    curl -sS https://platform-api.nomono.co/v1/sessions \  -H "Authorization: Bearer $NOMONO_API_KEY" \  -H "Content-Type: application/json" \  -o session.json \  -d @- <<EOF{"sources": [{"source_id": "$SOURCE_ID", "role": "microphone", "label": "Host"}]}EOFSESSION_ID=$(jq -r .session_id session.json)
  2. Create a job

    This job enhances the voice with the listening.full preset and returns the track as WAV. It's billed for the length of the session, in minutes.

    curl -sS https://platform-api.nomono.co/v1/jobs \  -H "Authorization: Bearer $NOMONO_API_KEY" \  -H "Content-Type: application/json" \  -o job.json \  -d @- <<EOF{  "session_id": "$SESSION_ID",  "products": {"enhancement": {"preset": "listening.full"}},  "outputs": [{"type": "tracks", "format": "wav"}]}EOFJOB_ID=$(jq -r .job_id job.json)
    201 Created
    {  "job_id": "job_01a14471b2d47f19b6a3c0e5d8f9a1b2",  "session_id": "ses_01a14471a03b7c56a1e28d9f4b6c7d8e",  "status": "queued",  "progress": 0.0,  "products": {"enhancement": {"preset": "listening.full"}},  "outputs_requested": [{"type": "tracks", "format": "wav"}],  "minutes": 30.57,  "created_at": "2026-10-16T09:13:02.071624Z",  "started_at": null,  "finished_at": null,  "error": null,  "retry_of": null}

    If you might retry this request, send it with an Idempotency-Key as described in Idempotency.

  3. Wait for the job

    Poll the job every few seconds until its status is done, failed or cancelled. Its progress goes from 0 to 1 along the way.

    STATUS=$(jq -r .status job.json)while [ "$STATUS" != done ] && [ "$STATUS" != failed ] && [ "$STATUS" != cancelled ]; do  sleep 5  curl -sS "https://platform-api.nomono.co/v1/jobs/$JOB_ID" \    -H "Authorization: Bearer $NOMONO_API_KEY" \    -o job.json  STATUS=$(jq -r .status job.json)  echo "$STATUS"done

    If the job fails, its error.message tells you what to do. Instead of polling, you can also use webhooks to be notified when a job ends.

Download the tracks

  1. List the outputs

    A tracks output gives you three files for each microphone: the WAV, an Opus copy and waveform data. Each file comes with a download URL that's valid for 24 hours.

    curl -sS "https://platform-api.nomono.co/v1/jobs/$JOB_ID/outputs" \  -H "Authorization: Bearer $NOMONO_API_KEY" \  -o outputs.jsonjq -r '.outputs[] | "\(.type) \(.format) \(.size_bytes)"' outputs.json

    The curl step prints:

    Output
    tracks wav 176084204tracks opus 21208733tracks waveform 21402

    Each output in the list looks like this:

    Output object
    {  "output_id": "out_01a144729c0e7b31a8f45d2c6e9b0a17",  "type": "tracks",  "label": "Host",  "source_id": "src_01a1446f235c7448abbe096c9b9adf6c",  "format": "wav",  "profile": null,  "size_bytes": 176084204,  "url": "https://...",  "expires_at": "2026-10-17T09:45:20Z"}
  2. Download a file

    Download a file with a plain GET of its url. Here that saves the enhanced track as host-enhanced.wav.

    curl -sS -o host-enhanced.wav \  "$(jq -r '.outputs[] | select(.type == "tracks" and .format == "wav") | .url' outputs.json)"

    To get fresh URLs, list the outputs again. The files themselves are kept until the session is deleted.

The whole script

Save the script below as quickstart.py and run it with the path of your file. It saves the enhanced track next to your file as host-enhanced.wav.

Shell
pip install requestspython quickstart.py host.wav
quickstart.py
import osimport sysimport timeimport requestsAPI_URL = os.environ.get("NOMONO_API_URL", "https://platform-api.nomono.co/v1")HEADERS = {"Authorization": f"Bearer {os.environ['NOMONO_API_KEY']}"}def create_upload(path: str) -> dict:    response = requests.post(        f"{API_URL}/uploads",        headers=HEADERS,        json={"filename": os.path.basename(path), "size_bytes": os.path.getsize(path)},    )    response.raise_for_status()    return response.json()def upload_parts(upload: dict, path: str) -> list[dict]:    parts = []    with open(path, "rb") as audio:        for part in upload["parts"]:            audio.seek((part["part_number"] - 1) * upload["part_size"])            # The URL is signed; the request carries no API key.            response = requests.put(part["url"], data=audio.read(upload["part_size"]))            response.raise_for_status()            parts.append({"part_number": part["part_number"], "etag": response.headers["ETag"]})    return partsdef complete_upload(upload_id: str, parts: list[dict]) -> dict:    response = requests.post(        f"{API_URL}/uploads/{upload_id}/complete",        headers=HEADERS,        json={"parts": parts},    )    response.raise_for_status()    return response.json()def create_session(source_id: str) -> dict:    response = requests.post(        f"{API_URL}/sessions",        headers=HEADERS,        json={"sources": [{"source_id": source_id, "role": "microphone", "label": "Host"}]},    )    response.raise_for_status()    return response.json()def create_job(session_id: str) -> dict:    response = requests.post(        f"{API_URL}/jobs",        headers=HEADERS,        json={            "session_id": session_id,            "products": {"enhancement": {"preset": "listening.full"}},            "outputs": [{"type": "tracks", "format": "wav"}],        },    )    response.raise_for_status()    return response.json()def wait_for_job(job_id: str) -> dict:    while True:        response = requests.get(f"{API_URL}/jobs/{job_id}", headers=HEADERS)        response.raise_for_status()        job = response.json()        print(f"{job['status']}, progress {job['progress']:.0%}")        if job["status"] in ("done", "failed", "cancelled"):            return job        time.sleep(5)def list_outputs(job_id: str) -> list[dict]:    response = requests.get(f"{API_URL}/jobs/{job_id}/outputs", headers=HEADERS)    response.raise_for_status()    return response.json()["outputs"]def download(url: str, path: str) -> None:    # The URL is signed; the request carries no API key.    with requests.get(url, stream=True) as response:        response.raise_for_status()        with open(path, "wb") as file:            for chunk in response.iter_content(chunk_size=1024 * 1024):                file.write(chunk)def main(path: str) -> None:    upload = create_upload(path)    parts = upload_parts(upload, path)    source = complete_upload(upload["upload_id"], parts)    print(f"Uploaded {source['source_id']}, {source['duration_s']} seconds")    session = create_session(source["source_id"])    job = create_job(session["session_id"])    print(f"Created {job['job_id']}")    job = wait_for_job(job["job_id"])    if job["status"] != "done":        sys.exit(f"The job is {job['status']}: {job['error']}")    for output in list_outputs(job["job_id"]):        print(output["type"], output["format"], output["size_bytes"])        if output["type"] == "tracks" and output["format"] == "wav":            enhanced_path = os.path.splitext(path)[0] + "-enhanced.wav"            download(output["url"], enhanced_path)            print(f"Saved {enhanced_path}")if __name__ == "__main__":    main(sys.argv[1])

Next steps

  • Sessions explains recordings with several microphones, a mixdown or a bed.
  • Jobs describes the presets, products and outputs you can ask for.
  • Webhooks shows how to get notified when a job ends.
  • The API reference lists every route.