Metadata-Version: 2.4
Name: musicapi-sdk
Version: 0.1.0
Summary: Official Python SDK for the MusicAPI music generation API
Project-URL: Homepage, https://musicapi.ai
Project-URL: Documentation, https://docs.musicapi.ai
Author-email: MusicAPI <support@musicapi.ai>
License: MIT
Keywords: ai,generation,music,musicapi,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.24.0
Provides-Extra: dev
Requires-Dist: anyio[trio]; extra == 'dev'
Requires-Dist: mypy<2,>=1.8; extra == 'dev'
Requires-Dist: pytest-anyio>=0.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: respx>=0.20; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# musicapi-sdk

Official Python SDK for the [MusicAPI](https://musicapi.ai) music generation API.
Fully typed, zero dependencies beyond `httpx`, Python 3.9+.

## Install

```bash
pip install musicapi-sdk
```

## Quickstart

Get an API key from the **MusicAPI dashboard**, then:

```python
from musicapi import MusicAPI, SonicCreateParams

client = MusicAPI(api_key="your-api-key")  # or set MUSICAPI_KEY env var

# Submit a generation job and wait for the finished song(s).
result = client.sonic.generate_and_wait(
    SonicCreateParams(
        custom_mode=False,
        mv="sonic-v5",
        gpt_description_prompt="uplifting synthwave with female vocals",
    ),
    # Optional: override poll interval / timeout
    # options=PollOptions(poll_interval_ms=5000, timeout_ms=300_000),
)

for song in result.data:
    print(song.title, song.audio_url)
```

Or submit and poll manually:

```python
from musicapi import MusicAPI, SonicCreateParams

client = MusicAPI(api_key="your-api-key")

submit = client.sonic.create(
    SonicCreateParams(
        custom_mode=True,
        mv="sonic-v5",
        prompt="[Verse]\nCity lights...\n[Chorus]\n...",
        title="Night Drive",
        tags="synthwave, retro",
    )
)

result = client.sonic.get_task(submit.task_id)
# result.code == 200 when terminal; 202 while still running
```

## Configuration

```python
MusicAPI(
    api_key="...",                                      # required
    base_url="https://api.musicapi.ai/api/v1",          # default
    timeout=60.0,                                       # seconds (default 60)
    max_retries=3,                                      # 429/5xx auto-retry (default 3)
    http_client=my_httpx_client,                        # optional custom transport
)
```

## Error handling

Every non-2xx response raises a `MusicAPIError`:

```python
from musicapi import MusicAPI, MusicAPIError, SonicCreateParams

client = MusicAPI(api_key="your-api-key")

try:
    balance = client.credits.get()
except MusicAPIError as err:
    print(err.status, err.code, str(err), err.request_id)
    if err.is_auth_error:       # 401 — bad API key
        ...
    if err.is_credit_error:     # 402/403 — insufficient credits
        ...
    if err.is_rate_limited:     # 429
        ...
```

- `429` and `5xx` are retried automatically with exponential backoff,
  honouring the `Retry-After` header (max 3 retries by default).
- Other `4xx` are terminal (raised immediately).
- `*_and_wait` helpers raise `MusicAPIError(code="client_timeout")` if the
  job does not finish within `options.timeout_ms` (default 5 minutes).

## Context manager

The client implements the context-manager protocol for clean connection cleanup:

```python
with MusicAPI(api_key="your-api-key") as client:
    balance = client.credits.get()
    print(balance.credits)
```

## Common examples

**Check credit balance** (`/get-credits`):

```python
balance = client.credits.get()
print(balance.credits, balance.extra_credits)
```

**Async generate-and-wait** (`/sonic/create` → `/sonic/task/{id}`):

```python
result = client.sonic.generate_and_wait(
    SonicCreateParams(
        custom_mode=False,
        mv="sonic-v5",
        gpt_description_prompt="lofi hip hop for studying",
    )
)
print(result.data[0].audio_url)
```

**Extend an existing clip**:

```python
submit = client.sonic.create(
    SonicCreateParams(
        task_type="extend_music",
        mv="sonic-v5",
        continue_clip_id="clip-uuid-here",
        continue_at=60.0,
    )
)
result = client.sonic.get_task(submit.task_id)
```

**Stem separation (basic 2-track)**:

```python
from musicapi import StemsParams

submit = client.sonic.stems_basic(StemsParams(clip_id="clip-uuid"))
# ... poll later ...
result = client.sonic.get_task(submit.task_id)
```

**Or use the blocking helper**:

```python
result = client.sonic.stems_basic_and_wait(StemsParams(clip_id="clip-uuid"))
```

**Producer / BGM generation**:

```python
from musicapi import ProducerCreateParams

result = client.producer.generate_and_wait(
    ProducerCreateParams(sound="calm piano ambient", length=60)
)
print(result.data[0].audio_url)
```

**Riffusion (FUZZ model)**:

```python
from musicapi import RiffusionCreateParams

result = client.riffusion.generate_and_wait(
    RiffusionCreateParams(prompt="electronic dance music", mv="FUZZ-1.1 Pro")
)
```

**Studio generation**:

```python
from musicapi import StudioCreateParams

result = client.studio.generate_and_wait(
    StudioCreateParams(
        prompt="upbeat jazz fusion",
        lyrics_type="instrumental",
        prompt_strength=0.8,
        clarity_strength=0.7,
        lyrics_strength=0.5,
        generation_quality=0.9,
        model_type="studio130-v1.5",
        config={"mode": "regular"},
    )
)
```

## API reference

All async generation endpoints return a `TaskSubmitResponse` with a `task_id`.
Pass it to the matching `get_task` method (the API answers HTTP 202 while still
running), or use the `*_and_wait` convenience helper.

### `client.sonic` — vocal music generation & editing

| Method | Endpoint | Description |
|---|---|---|
| `create(params)` | POST `/sonic/create` | Submit a generation (create/extend/cover/concat/persona/remaster/add_instrumental/add_vocals). Async. |
| `get_task(id)` | GET `/sonic/task/{id}` | Poll a sonic task. |
| `generate_and_wait(params, options)` | submit + poll | Submit and resolve with the finished song(s). |
| `lyrics(params)` | POST `/sonic/lyrics` | Generate 2 candidate songs (title + lyrics). Sync. |
| `aligned_lyrics(params)` | POST `/sonic/aligned-lyrics` | Word/line lyric timing for a clip. Sync. |
| `bpm(params)` | POST `/sonic/bpm` | Estimate avg/min/max BPM. Sync. |
| `upsample_tags(params)` | POST `/sonic/upsample-tags` | Expand a tag string. Sync. |
| `create_voice(params)` | POST `/sonic/create-voice` | Train a voice persona from audio. Async. |
| `create_persona(params)` | POST `/sonic/persona` | Create a persona from a clip. Sync. |
| `vox(params)` | POST `/sonic/vox` | Extract a vocal segment. Sync. |
| `midi(params)` | POST `/sonic/midi` | MIDI transcription. Sync (may 202 while building). |
| `wav(params)` | POST `/sonic/wav` | Get a WAV download URL. Sync. |
| `stems_basic(params)` / `stems_basic_and_wait` | POST `/sonic/stems/basic` | 2-track stem separation. Async. |
| `stems_full(params)` / `stems_full_and_wait` | POST `/sonic/stems/full` | Multi-track stem separation. Async. |
| `mashup(params)` / `mashup_and_wait` | POST `/sonic/mashup` | Blend two clips. Async. |
| `replace_section(params)` / `replace_section_and_wait` | POST `/sonic/replace-section` | Regenerate a section (infill). Async. |
| `sample(params)` | POST `/sonic/sample` | Chop a sample window and generate from it. Async. |
| `upload(params)` | POST `/sonic/upload` | Import external audio → `clip_id`. Sync. |
| `upload_cover(params)` | POST `/sonic/upload-cover` | One-shot upload + cover. Async. |
| `upload_extend(params)` | POST `/sonic/upload-extend` | One-shot upload + extend. Async. |

### `client.suno` — legacy aliases & helpers

| Method | Endpoint | Description |
|---|---|---|
| `create(params)` | POST `/suno/create` | Alias of `sonic.create`. |
| `get_clip(id)` | GET `/suno/clip/{id}` | Alias of `sonic.get_task`. |
| `generate_and_wait(params, options)` | submit + poll | Submit via `/suno/create`, poll `/suno/clip`. |
| `make_lyrics(params)` | POST `/suno/make-lyrics` | Legacy async lyric generation. |
| `get_lyrics(id)` | GET `/suno/get-lyrics/{id}` | Fetch generated lyrics by task id. |
| `get_song(params)` | POST `/suno/get-song` | Scrape metadata from a Suno share link. |

### `client.producer` — instrumental / BGM generation

| Method | Endpoint | Description |
|---|---|---|
| `create(params)` / `generate_and_wait` | POST `/producer/create` | Generate BGM. Async. |
| `get_task(id)` | GET `/producer/task/{id}` | Poll a producer task. |
| `upload(params)` | POST `/producer/upload` | Import external audio. Async. |
| `download(params)` / `download_and_wait` | POST `/producer/download` | Request mp3/wav download. Async. |

### `client.riffusion` — FUZZ-model generation

| Method | Endpoint | Description |
|---|---|---|
| `create(params)` / `generate_and_wait` | POST `/riffusion/create` | FUZZ-model generation. Async. |
| `get_task(id)` | GET `/riffusion/task/{id}` | Poll a riffusion task. |
| `upload(params)` | POST `/riffusion/upload` | Import external audio → `clip_id`. Sync. |

### `client.studio` — studio generation

| Method | Endpoint | Description |
|---|---|---|
| `create(params)` / `generate_and_wait` | POST `/studio/create` | Submit a studio job. Async. |
| `get_task(id)` | GET `/studio/task/{id}` | Poll a studio task (terminal at `status == "SUCCESS"`). |

### `client.credits` — account

| Method | Endpoint | Description |
|---|---|---|
| `get()` | GET `/get-credits` | Return `{ credits, extra_credits }`. |

## OpenAPI

A machine-readable OpenAPI 3.1 spec covering every endpoint is included at
[`openapi.yaml`](./openapi.yaml).

## Development

```bash
# Install in editable mode with dev extras
pip install -e ".[dev]"

# Run tests
python -m pytest tests/ -v

# Type-check
mypy src/musicapi

# Lint
ruff check src/ tests/
```

## Publishing (maintainers)

```bash
pip install build twine
python -m build
twine upload dist/*
```

The package name on PyPI is `musicapi-sdk` (note: the top-level import is
`import musicapi`). See the note on package naming in the source.

## License

MIT
