SFX Loom
Sound effectsBlogAPIPricingLicense
Log inStart free
SFX Loom

Sound effects for games, generated from a text prompt.

Product

ExamplesSound effectsBlogAPIPricingSound licenseCreate account

Sound effects

WeaponsMagic & spellsUI & menusFootsteps & foleyImpacts & explosionsSci-fi

Legal

Terms of servicePrivacy policyRefund policy

Contact

support@sfxloom.com

© 2026 SFX Loom

Payments are processed by Lemon Squeezy, our Merchant of Record.

Blog

How to Generate Game Sound Effects with an API (Python and JavaScript)

November 4, 2026 · 7 min read

Clicking through a web app is fine for ten sounds. When you need fifty placeholder effects for a greybox level, want a menu in your engine that generates audio, or keep a sound list in your repo, you want to generate sound effects from code. This guide shows the whole flow with the SFX Loom sound effects API, in Python and JavaScript.

The flow is three requests:

  1. POST /api/v1/generations starts generating a sound from your prompt.
  2. GET /api/v1/generations/{id} tells you when it’s done (usually 5–30 seconds).
  3. GET /api/v1/sounds/{id}/download?format=wav gives you the file.

1. Create an API key

Sign in, open API in the app and click Create key. The full key starts with sfxl_ and is shown only once, so copy it straight into an environment variable:

export SFXLOOM_API_KEY="sfxl_..."

The key spends your credits, so treat it like a password. Keep it on your machine, your build server or your backend. Never put it in a game build, a website or a mobile app: anyone can pull strings out of a shipped binary. Generate the sounds during development and ship the audio files.

2. Start a generation

Send a prompt, an optional model, model settings and how many takes you want (1–4):

import os
import requests

API = "https://sfxloom.com/api/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SFXLOOM_API_KEY']}"

res = session.post(f"{API}/generations", json={
    "model": "elevenlabs/sound-effects-v2",
    "prompt": "Sword being drawn from a metal sheath, sharp metallic slide, "
              "clean ringing tone at the end, close-up, no music, no background noise",
    "params": {"duration": 2},
    "variations": 2,
})
gen = res.json()
print(res.status_code, gen["id"], gen["status"], gen["credits"])

The response is 202 Accepted with the generation still processing. At this point the full cost is reserved from your balance and shown in credits.reserved. If your balance is too low you get 402 insufficient_credits and nothing is charged.

Settings are strict on purpose: an unknown parameter, a value out of range or a duration off the slider step (the default model uses 0.5-second steps from 0.5 to 22 seconds) returns 400 invalid_params instead of being silently changed. GET /api/v1/models lists every model with its parameters and prices.

3. Poll until it’s done

Ask for the generation every few seconds. Each call also moves it forward: finished takes are saved to your library and, once every take is done, credits are settled.

import time

while gen["status"] in ("queued", "processing"):
    time.sleep(3)
    gen = session.get(f"{API}/generations/{gen['id']}").json()

print(gen["status"], gen["credits"])
# completed {'reserved': 2, 'charged': 2, 'refunded': 0}

completed means at least one take succeeded; failed means none did. Every take that fails is refunded automatically, so you only pay for sounds you actually get. If your script crashes mid-way, nothing is lost: the generation still finishes and appears in your library, and unfinished work is refunded within 15 minutes.

4. Download as WAV

Each completed take has a sound_id. Ask for the format your engine wants: wav (16-bit PCM, best for short effects), ogg (small, great for loops) or mp3. Not sure which? WAV vs OGG vs MP3 explains the trade-offs.

for i, take in enumerate(gen["variations"], start=1):
    if take["status"] != "completed":
        continue
    audio = session.get(
        f"{API}/sounds/{take['sound_id']}/download",
        params={"format": "wav"},
    )
    audio.raise_for_status()
    with open(f"sword_draw_{i}.wav", "wb") as f:
        f.write(audio.content)

When the requested format is the one stored, the endpoint answers with a redirect to a short-lived signed file URL. Python requests and fetch follow it for you; with curl add -L.

The same thing in JavaScript

Node 18 or newer has fetch built in, so there’s nothing to install:

import { writeFile } from "node:fs/promises";

const API = "https://sfxloom.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.SFXLOOM_API_KEY}` };

async function call(path, init = {}) {
  const res = await fetch(API + path, { ...init, headers: { ...headers, ...init.headers } });
  if (!res.ok) {
    const { error } = await res.json();
    throw new Error(`${res.status} ${error.code}: ${error.message}`);
  }
  return res;
}

let gen = await (
  await call("/generations", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      prompt: "Magic fireball spell cast, whoosh building up then fiery burst, fantasy game, no music",
      params: { duration: 3 },
      variations: 2,
    }),
  })
).json();

while (gen.status === "queued" || gen.status === "processing") {
  await new Promise((r) => setTimeout(r, 3000));
  gen = await (await call(`/generations/${gen.id}`)).json();
}

let i = 0;
for (const take of gen.variations.filter((v) => v.status === "completed")) {
  const audio = await call(`/sounds/${take.sound_id}/download?format=wav`);
  await writeFile(`fireball_${++i}.wav`, Buffer.from(await audio.arrayBuffer()));
}

Handle errors properly

Every error has the same shape, { "error": { "code": "...", "message": "..." } }, so you can branch on the stable code:

  • 402 insufficient_credits: nothing was charged. Top up and try again.
  • 429 rate_limited or key_rate_limited: wait for the number of seconds in the Retry-After header, then retry. A rejected request is never charged.
  • 400 prompt_blocked: the prompt isn’t allowed (for example imitating a real person’s voice). Rewrite it.
  • 500 internal: safe to retry GET requests.

One rule matters for your wallet: if a POST /generations times out on the network, don’t blindly send it again. The first request may have started (and reserved credits for) a generation. Check your library or recent generations first.

Prompts still matter

The API uses the same models as the web app, so the same prompt rules apply: name the source, the action, the material and the distance, and add “no music, no background noise”. The prompt writing guide has templates for every category, and the sound effects library has prompts that already work.

What to build next

  • Generate a whole sound list from a CSV file: batch-generate sound effects with a script.
  • Generate sounds without leaving your engine: a Unity editor window for the API.
  • Read the full reference, prices and limits in the API documentation.

Generate game sound effects from a prompt

5 free credits · no card required

Start free

More guides

  • Batch-Generate Every Sound Effect in Your Game with One Script
  • Generate Sound Effects Inside the Unity Editor with AI
  • How to Write Prompts for AI Sound Effects (With Examples)