> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wav.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Free stem presets guide

> Integrate stem_preset and get the most out of free stem separation in November 2026.

No Cost November
**Stem presets are free on paid plans until Nov 30, 23:59:59 UTC.** Up to 10 free requests per hour and 100 per day.
[Extraction reference →](https://docs.wav.com/api-documentation/conversions/extraction.md)

During November 2026, [Extraction](https://docs.wav.com/api-documentation/conversions/extraction.md) requests made with the new `stem_preset` field are free on every paid plan. This guide shows you how to integrate `stem_preset`, how to tell whether a request was free, and how to keep your workload inside the free quota.

## At a glance

| | |
| - | - |
| **Window** | `2026-11-01T00:00:00Z` to `2026-11-30T23:59:59Z` |
| **Who** | Pay as you go, API Plus, API Pro, and Enterprise accounts. The Free plan is not included. |
| **What is free** | `POST /v2/Extraction` requests that use `stem_preset` (`2_stems` or `6_stems`). The whole request is free, including `preprocessing_options` and `eco_mode`. |
| **Limits** | 10 free requests per UTC clock hour and 100 per UTC day, shared by all API Keys on your account. |
| **Past the limits** | The request still runs. It is charged at the usual [stem splitter rates](https://docs.wav.com/api-documentation/index/pricing.md#stem-splitter-features-pricing). |
| **Not included** | Requests that list `stems`, even a list that matches a preset exactly. |

## Quick start

**1. Use an API Key from a paid plan**

Create or copy an API Key in the [API dashboard](https://wav.com/api-dashboard). Requests from Free plan accounts are charged as usual. See [Pricing](https://docs.wav.com/api-documentation/index/pricing.md) to upgrade.

**2. Send stem\_preset instead of stems**

Replace your `stems` field with `stem_preset`. Leave `stems` out, or send `[]`.

```bash theme={null}
curl -X POST "https://api.wav.com/api/public/v2/Extraction" \
-H "Authorization: <api_key>" \
-F "audio_url=<YOUR_AUDIO_URL>" \
-F "stem_preset=6_stems" \
-F "webhook_url=https://example.com/webhook"
```

**3. Check that the request was free**

A free request returns `"is_free_promo": true` and `"credit_estimate": 0.0`, plus the free requests you have left.

```json theme={null}
{
  "success": true,
  "task_id": "5b0e6f1c-2d7a-4f63-9a51-3c8e2b7d9f10",
  "conversion_id": "a41c9e57-0b3d-4c2e-8f6a-7d1e5b2c9a84",
  "eta": 147,
  "credit_estimate": 0.0,
  "message": "Message published to queue",
  "status": "IN_QUEUE",
  "is_free_promo": true,
  "free_generations_remaining_hour": 9,
  "free_generations_remaining_day": 99,
  "free_quota_resets_at": "2026-11-03T15:00:00Z"
}
```

**4. Collect your stems**

The [webhook](https://docs.wav.com/api-documentation/index/webhook.md) and [Get by ID](https://docs.wav.com/api-documentation/endpoint/getById.md) return one URL per stem, exactly as they do for a `stems` request.

## Pick the right preset

Each preset counts as one request against your free quota, however many stems it returns.

| `stem_preset` | Stems you get back | Use it when you need |
| - | - | - |
| `2_stems` | `vocals`, `instrumental` | Acapellas, karaoke and backing tracks, vocal remixes, lyric sync |
| `6_stems` | `vocals`, `instrumental`, `piano`, `bass`, `drums`, `guitar` | Any of drums, bass, piano, or guitar on their own, practice tracks, sampling, remix stems |

> [!NOTE]
>
> Finer splits such as `lead_vocal`, `male_vocal`, `kick_drum`, `acoustic_guitar`, `strings`, or `winds` have no preset. Request them with `stems`, which is charged as usual.

## Read the free quota from the response

Every Extraction response now includes four promotion fields. Use them instead of counting requests yourself: the counters live on our servers and use UTC.

| Field | Type | Meaning |
| - | - | - |
| `is_free_promo` | `boolean` | `true` when this request is free. No credits are charged. |
| `free_generations_remaining_hour` | `integer \| null` | Free requests left in the current UTC clock hour. |
| `free_generations_remaining_day` | `integer \| null` | Free requests left today (UTC). |
| `free_quota_resets_at` | `string \| null` | When more free requests become available: the start of the next UTC hour, or of the next UTC day once the daily limit is used up. |

What the fields look like in each situation:

| Situation | `is_free_promo` | `credit_estimate` | Quota fields |
| - | - | - | - |
| Paid plan, `stem_preset`, quota left | `true` | `0.0` | Counts after this request, and the next reset time |
| Paid plan, `stem_preset`, hourly or daily limit reached | `false` | Usual price | The used-up count is `0`. `free_quota_resets_at` says when to retry for free |
| `stems` list, Free plan, or outside November | `false` | Usual price | All `null` |

> [!WARNING]
>
> Requests past the limits are **charged, not rejected**. If you want to stay free, stop sending preset requests when a remaining count reaches `0`, and resume at `free_quota_resets_at`.

## Get the most out of the promotion

> [accordion] Send stem\_preset, never the equivalent stems list
>
> The promotion is tied to the `stem_preset` field. A request with `stems=["vocals","instrumental"]` returns the same stems as `2_stems` but is charged.

> [accordion] Use 6\_stems when you need more than vocals
>
> One `6_stems` request uses one free slot and returns six stems. Splitting the same track with several smaller requests uses more of your quota.

> [accordion] Add preprocessing and keep full quality
>
> The whole request is free, so `preprocessing_options` such as `["Denoise", "Dereverb"]` cost nothing extra. Leave `eco_mode` at `false` for the highest quality output.

> [accordion] Pace your batch with free\_quota\_resets\_at
>
> Up to 10 free requests per hour adds up to 100 a day. Queue your tracks and send them as the quota resets instead of all at once. The code below does this for you.

> [accordion] Use webhooks instead of polling
>
> Pass `webhook_url` so results arrive as soon as each conversion finishes. Extraction counts towards your [parallel request limit](https://docs.wav.com/api-documentation/utilities/ratelimits.md), so avoid holding slots open with long polling loops.

> [accordion] Keep stem\_preset after November
>
> `stem_preset` stays available after the promotion ends. From December 1, preset requests are charged like the matching `stems` list, so you don't need to change your code back.

## Batch tracks inside the free quota

These examples send one preset request per track. After each response they check the remaining counts, wait until `free_quota_resets_at` when either one reaches `0`, and stop if a request comes back charged.

```python Python theme={null}
import time
from datetime import datetime, timezone

import requests

API_URL = "https://api.wav.com/api/public/v2/Extraction"
API_KEY = "<API_KEY>"


def extract(audio_url, preset="6_stems", webhook_url=None):
    data = {"audio_url": audio_url, "stem_preset": preset}
    if webhook_url:
        data["webhook_url"] = webhook_url
    response = requests.post(
        API_URL, headers={"Authorization": API_KEY}, data=data, timeout=60
    )
    response.raise_for_status()
    return response.json()


def seconds_until(timestamp):
    reset_at = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
    return max((reset_at - datetime.now(timezone.utc)).total_seconds(), 0) + 1


def extract_all_free(audio_urls, preset="6_stems", webhook_url=None):
    results = []
    for audio_url in audio_urls:
        result = extract(audio_url, preset, webhook_url)
        results.append(result)
        if not result["is_free_promo"]:
            print(f"{result['task_id']}: charged {result['credit_estimate']}. Stopping.")
            break  # Free quota used elsewhere, or no promotion applies.
        print(f"{result['task_id']}: free")

        hour_left = result["free_generations_remaining_hour"]
        day_left = result["free_generations_remaining_day"]
        if hour_left == 0 or day_left == 0:
            wait = seconds_until(result["free_quota_resets_at"])
            print(f"Free quota used up. Waiting {wait:.0f}s.")
            time.sleep(wait)
    return results


extract_all_free(
    ["<TRACK_1_URL>", "<TRACK_2_URL>", "<TRACK_3_URL>"],
    webhook_url="https://example.com/webhook",
)
```

```javascript Node.js theme={null}
const API_URL = "https://api.wav.com/api/public/v2/Extraction";
const API_KEY = "<API_KEY>";

async function extract(audioUrl, preset = "6_stems", webhookUrl) {
  const form = new FormData();
  form.append("audio_url", audioUrl);
  form.append("stem_preset", preset);
  if (webhookUrl) form.append("webhook_url", webhookUrl);

  const response = await fetch(API_URL, {
    method: "POST",
    headers: { Authorization: API_KEY },
    body: form,
  });
  if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
  return response.json();
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function extractAllFree(audioUrls, preset = "6_stems", webhookUrl) {
  const results = [];
  for (const audioUrl of audioUrls) {
    const result = await extract(audioUrl, preset, webhookUrl);
    results.push(result);
    if (!result.is_free_promo) {
      console.log(`${result.task_id}: charged ${result.credit_estimate}. Stopping.`);
      break; // Free quota used elsewhere, or no promotion applies.
    }
    console.log(`${result.task_id}: free`);

    const hourLeft = result.free_generations_remaining_hour;
    const dayLeft = result.free_generations_remaining_day;
    if (hourLeft === 0 || dayLeft === 0) {
      const wait = Math.max(Date.parse(result.free_quota_resets_at) - Date.now(), 0) + 1000;
      console.log(`Free quota used up. Waiting ${Math.round(wait / 1000)}s.`);
      await sleep(wait);
    }
  }
  return results;
}

extractAllFree(
  ["<TRACK_1_URL>", "<TRACK_2_URL>", "<TRACK_3_URL>"],
  "6_stems",
  "https://example.com/webhook"
).catch(console.error);
```

> [!TIP]
>
> If several workers or services share one account, they share one quota. Run the batch from a single worker. Requests sent elsewhere on the account can use a free slot first, so the next request may be charged.

## Errors

| Status | When |
| - | - |
| `422 UNPROCESSABLE_CONTENT` | `stem_preset` is not `2_stems` or `6_stems`, or you sent both `stem_preset` and a non-empty `stems` list. The message lists the valid presets. |
| `402 INSUFFICIENT_CREDITS` | A charged request (for example, past the free limits) and your balance is too low. Free requests don't need credits. |
| `429 TOO_MANY_REQUESTS` | You reached your plan's [parallel request limit](https://docs.wav.com/api-documentation/utilities/ratelimits.md). Wait for a conversion to finish, then retry. |

Requests rejected with `422` don't use a free slot. Neither do requests that fail before they are queued.

## FAQ

> [accordion] I sent stems=\["vocals","instrumental"]. Why was I charged?
>
> Only requests that use the `stem_preset` field are free. Send `stem_preset=2_stems` instead.

> [accordion] Is the limit per API Key?
>
> No. The 10 per hour and 100 per day limits apply to your account, across all of its API Keys.

> [accordion] When does the hourly limit reset?
>
> At the start of every UTC clock hour, for example at 15:00:00Z. It is not a rolling 60-minute window. The daily limit resets at 00:00:00Z.

> [accordion] What happens to my 11th request in an hour?
>
> It runs and is charged at the usual stem splitter rate. The response shows `"is_free_promo": false` and `"free_generations_remaining_hour": 0`.

> [accordion] I'm on the Free plan. Can I use stem\_preset?
>
> Yes. Presets work on every plan, but only paid plans get them free. See [Pricing](https://docs.wav.com/api-documentation/index/pricing.md) to upgrade.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.