@runapi.ai/suno 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +43 -0
- package/dist/index.d.mts +531 -0
- package/dist/index.d.ts +531 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +541 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +79 -0
- package/skills/suno/README.md +58 -0
- package/skills/suno/SKILL.md +238 -0
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@runapi.ai/suno",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "RunAPI Suno SDK for JavaScript, Ruby, and Go",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"skills",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"clean": "rm -rf dist",
|
|
29
|
+
"test:simple": "dotenv -e manual-tests/.env -- tsx manual-tests/test-generation-simple.ts",
|
|
30
|
+
"test:custom": "dotenv -e manual-tests/.env -- tsx manual-tests/test-generation-custom.ts",
|
|
31
|
+
"test:covers": "dotenv -e manual-tests/.env -- tsx manual-tests/test-covers.ts",
|
|
32
|
+
"test:extension": "dotenv -e manual-tests/.env -- tsx manual-tests/test-extension.ts",
|
|
33
|
+
"test:instrumentals": "dotenv -e manual-tests/.env -- tsx manual-tests/test-instrumentals.ts",
|
|
34
|
+
"test:vocals": "dotenv -e manual-tests/.env -- tsx manual-tests/test-vocals.ts",
|
|
35
|
+
"test:vocal-removal": "dotenv -e manual-tests/.env -- tsx manual-tests/test-vocal-removal.ts",
|
|
36
|
+
"test:conversions": "dotenv -e manual-tests/.env -- tsx manual-tests/test-conversions.ts",
|
|
37
|
+
"test:music-videos": "dotenv -e manual-tests/.env -- tsx manual-tests/test-music-videos.ts",
|
|
38
|
+
"test:lyrics": "dotenv -e manual-tests/.env -- tsx manual-tests/test-lyrics.ts",
|
|
39
|
+
"test:timestamped-lyrics": "dotenv -e manual-tests/.env -- tsx manual-tests/test-timestamped-lyrics.ts",
|
|
40
|
+
"test:section-replacements": "dotenv -e manual-tests/.env -- tsx manual-tests/test-section-replacements.ts",
|
|
41
|
+
"test:personas-styles": "dotenv -e manual-tests/.env -- tsx manual-tests/test-personas-styles.ts",
|
|
42
|
+
"test:midi": "dotenv -e manual-tests/.env -- tsx manual-tests/test-midi.ts",
|
|
43
|
+
"test:errors": "dotenv -e manual-tests/.env -- tsx manual-tests/test-error-handling.ts",
|
|
44
|
+
"test:all": "dotenv -e manual-tests/.env -- tsx manual-tests/test-all.ts",
|
|
45
|
+
"test:manual": "npm run test:all"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@runapi.ai/core": "^0.2.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.0.0",
|
|
52
|
+
"@vitest/coverage-v8": "^1.6.0",
|
|
53
|
+
"dotenv-cli": "^11.0.0",
|
|
54
|
+
"tsup": "^8.0.0",
|
|
55
|
+
"tsx": "^4.7.0",
|
|
56
|
+
"typescript": "^5.4.0",
|
|
57
|
+
"vitest": "^1.6.0"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"keywords": [
|
|
63
|
+
"runapi",
|
|
64
|
+
"runapi-ai",
|
|
65
|
+
"suno",
|
|
66
|
+
"api",
|
|
67
|
+
"sdk",
|
|
68
|
+
"typescript",
|
|
69
|
+
"ruby",
|
|
70
|
+
"golang"
|
|
71
|
+
],
|
|
72
|
+
"author": "RunAPI",
|
|
73
|
+
"license": "Apache-2.0",
|
|
74
|
+
"homepage": "https://runapi.ai/models/suno",
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "git+https://github.com/runapi-ai/suno-sdk.git"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Suno AI API Skill for RunAPI
|
|
2
|
+
|
|
3
|
+
Generate music, lyrics, stems, covers, and music videos with the Suno SDK. This skill helps Claude Code, Codex, Gemini CLI, Cursor, and 50+ agents integrate Suno through RunAPI.
|
|
4
|
+
|
|
5
|
+
The canonical agent file is `skills/suno/SKILL.md`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx skills add runapi-ai/suno -g
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or manually: clone this repo and copy `skills/suno/` into your agent's skills directory.
|
|
14
|
+
|
|
15
|
+
## Quick example
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { SunoClient } from '@runapi.ai/suno';
|
|
19
|
+
|
|
20
|
+
const client = new SunoClient();
|
|
21
|
+
const result = await client.textToMusic.run({
|
|
22
|
+
custom_mode: false,
|
|
23
|
+
instrumental: false,
|
|
24
|
+
prompt: 'A chill lo-fi beat with soft vocals',
|
|
25
|
+
model: 'V5',
|
|
26
|
+
});
|
|
27
|
+
const audioUrl = result.audios[0].audio_url;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Routing
|
|
31
|
+
|
|
32
|
+
- Model page: https://runapi.ai/models/suno
|
|
33
|
+
- Product docs: https://runapi.ai/docs#suno
|
|
34
|
+
- SDK docs: https://runapi.ai/docs#sdk-suno
|
|
35
|
+
- SDK repository: https://github.com/runapi-ai/suno-sdk
|
|
36
|
+
- Pricing and rate limits: https://runapi.ai/models/suno/v3.5
|
|
37
|
+
- Provider comparison: https://runapi.ai/providers/suno
|
|
38
|
+
- Browse all RunAPI models and skills: https://runapi.ai/models
|
|
39
|
+
|
|
40
|
+
## Variants
|
|
41
|
+
|
|
42
|
+
- [v3.5](https://runapi.ai/models/suno/v3.5)
|
|
43
|
+
- [v4](https://runapi.ai/models/suno/v4)
|
|
44
|
+
- [v4.5](https://runapi.ai/models/suno/v4.5)
|
|
45
|
+
- [v4.5 all](https://runapi.ai/models/suno/v4.5-all)
|
|
46
|
+
- [v4.5 plus](https://runapi.ai/models/suno/v4.5-plus)
|
|
47
|
+
- [v5](https://runapi.ai/models/suno/v5)
|
|
48
|
+
- [v5.5](https://runapi.ai/models/suno/v5.5)
|
|
49
|
+
|
|
50
|
+
## Agent rules
|
|
51
|
+
|
|
52
|
+
- Keep API keys in `RUNAPI_API_KEY` or RunAPI CLI config; never commit secrets.
|
|
53
|
+
- Prefer `create`, `get`, and `run` JSON passthrough patterns instead of inventing flags for every model parameter.
|
|
54
|
+
- For suno ai api pricing, rate-limit, and commercial-usage answers, link to the variant page rather than the repository README.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
Licensed under the Apache License, Version 2.0.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: suno
|
|
3
|
+
description: Generate music (simple or custom mode, cover, extension, vocals/instrumentals, audio stem separation, stems, MIDI, WAV, music videos, lyrics, personas) through RunAPI.ai using the @runapi.ai/suno Node/TypeScript SDK. Use when the user asks to add AI music generation, mentions Suno, or writes against @runapi.ai/suno. Triggers on "suno", "music generation", "生成音乐", "AI 音乐", "@runapi.ai/suno".
|
|
4
|
+
documentation: https://runapi.ai/models/suno
|
|
5
|
+
provider_page: https://runapi.ai/providers/suno
|
|
6
|
+
catalog: https://runapi.ai/models
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# @runapi.ai/suno — RunAPI.ai Suno music generation
|
|
10
|
+
|
|
11
|
+
Build Node / TypeScript integrations that generate music, lyrics, stems, and music videos through RunAPI.ai.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
Requires **Node 18+** (global `fetch`).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @runapi.ai/suno
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Set your API key in the environment:
|
|
22
|
+
|
|
23
|
+
```dotenv
|
|
24
|
+
# .env
|
|
25
|
+
RUNAPI_API_KEY=runapi_xxx # get one at https://runapi.ai/settings/api_keys
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { SunoClient } from '@runapi.ai/suno';
|
|
30
|
+
|
|
31
|
+
// The SDK reads RUNAPI_API_KEY from the environment automatically.
|
|
32
|
+
const client = new SunoClient();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pass `{ apiKey }` explicitly if you manage secrets differently. `baseUrl` defaults to `https://runapi.ai`; override only for local development.
|
|
36
|
+
|
|
37
|
+
## Core recipe — music generation
|
|
38
|
+
|
|
39
|
+
Two modes share one endpoint. Use `custom_mode: false` for quick results; `custom_mode: true` when you want control over style, title, and persona.
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// Simple mode — prompt drives everything.
|
|
43
|
+
const result = await client.textToMusic.run({
|
|
44
|
+
custom_mode: false,
|
|
45
|
+
instrumental: false,
|
|
46
|
+
prompt: 'A chill lo-fi beat with soft vocals',
|
|
47
|
+
model: 'V5',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const audioUrl = result.audios[0].audio_url;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// Custom mode — style, title, and prompt are all required.
|
|
55
|
+
await client.textToMusic.run({
|
|
56
|
+
custom_mode: true,
|
|
57
|
+
instrumental: false,
|
|
58
|
+
style: 'Jazz, Bossa Nova',
|
|
59
|
+
title: 'Midnight Blues',
|
|
60
|
+
prompt: 'Soft vocals over a walking bassline...',
|
|
61
|
+
model: 'V5',
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`run()` creates the task, auto-polls, and resolves only when the task completes — `audios[0].audio_url` is guaranteed on the resolved value. On failure it throws `TaskFailedError`; on polling timeout it throws `TaskTimeoutError`. Suno tasks take several minutes, so split `run()` into `create()` + webhook / `get()` for web handlers:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const { id } = await client.textToMusic.create({ custom_mode: false, instrumental: false, prompt: '...', model: 'V5' });
|
|
69
|
+
// return 202 immediately; fetch later:
|
|
70
|
+
const status = await client.textToMusic.get(id);
|
|
71
|
+
if (status.status === 'completed') { /* ... */ }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`run()` polls every 2 s for up to 15 min by default. Tune for long tracks:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
await client.textToMusic.run(params, { maxWaitMs: 30 * 60_000, pollIntervalMs: 5_000 });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
If `TaskTimeoutError` fires, the task is still running server-side — resume with `textToMusic.get(id)` or finish via webhook.
|
|
81
|
+
|
|
82
|
+
## Other resources
|
|
83
|
+
|
|
84
|
+
`SunoClient` exposes these resources; each has the same `create / get / run` shape:
|
|
85
|
+
|
|
86
|
+
| Resource | Purpose |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `textToMusic` | Core text-to-music |
|
|
89
|
+
| `extendMusic` | Extend an existing generated track |
|
|
90
|
+
| `extendMusic` | Extend an uploaded audio file |
|
|
91
|
+
| `generateArtwork` | Generate cover images for an existing track |
|
|
92
|
+
| `coverAudio` | Restyle an uploaded track |
|
|
93
|
+
| `addInstrumental` | Add instrumental backing to a vocal upload (V4_5PLUS / V5) |
|
|
94
|
+
| `addVocals` | Add vocals to an instrumental upload (V4_5PLUS / V5) |
|
|
95
|
+
| `separateAudioStems` | Split vocals from instrumentals (or up to 12 stems) |
|
|
96
|
+
| `generateMidi` | Extract MIDI from a completed `split_stem` audio stem separation |
|
|
97
|
+
| `convertAudio` | Convert a generated audio to WAV |
|
|
98
|
+
| `visualizeMusic` | Render a music video for a generated audio |
|
|
99
|
+
| `generateLyrics` / `getTimestampedLyrics` | Generate lyrics / word-level timing |
|
|
100
|
+
| `replaceSection` | Regenerate a time range of an existing track |
|
|
101
|
+
| `createMashup` | Blend two uploaded tracks |
|
|
102
|
+
| `generatePersona` / `boostStyle` | Create reusable vocal personas / style descriptors |
|
|
103
|
+
|
|
104
|
+
### Upload-and-cover — restyle an external track
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const restyled = await client.coverAudio.run({
|
|
108
|
+
upload_url: 'https://cdn.example.com/song.mp3',
|
|
109
|
+
custom_mode: false,
|
|
110
|
+
instrumental: false,
|
|
111
|
+
prompt: 'Transform into a jazz cover',
|
|
112
|
+
model: 'V4_5PLUS',
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Extension — continue a generated track
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const base = await client.textToMusic.run({ custom_mode: false, instrumental: false, prompt: '...', model: 'V5' });
|
|
120
|
+
await client.extendMusic.run({
|
|
121
|
+
audio_id: base.audios[0].id,
|
|
122
|
+
default_param_flag: false,
|
|
123
|
+
model: 'V5',
|
|
124
|
+
prompt: 'Continue with an uplifting chorus',
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Vocal removal — separate stems
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const gen = await client.textToMusic.run({ custom_mode: false, instrumental: false, prompt: '...', model: 'V5' });
|
|
132
|
+
const sep = await client.separateAudioStems.run({
|
|
133
|
+
task_id: gen.id,
|
|
134
|
+
audio_id: gen.audios[0].id,
|
|
135
|
+
type: 'split_stem', // or 'separate_vocal' for just vocals + instrumental
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(sep.separated_audios.vocal_url);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Models
|
|
142
|
+
|
|
143
|
+
| `model` | Notes |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `V5` | Latest model; best musical expression. |
|
|
146
|
+
| `V4_5PLUS` | Rich sound, max 8 min. |
|
|
147
|
+
| `V4_5ALL` | All features, max 8 min (audio upload max 1 min). |
|
|
148
|
+
| `V4_5` | Balanced quality and speed. |
|
|
149
|
+
| `V4` | Stable, max 4 min. |
|
|
150
|
+
| `V3_5` | Legacy. |
|
|
151
|
+
|
|
152
|
+
Instrumental / vocal endpoints require `V4_5PLUS` or `V5`. Exact credit costs per model are shown at https://runapi.ai/pricing and in the dashboard — do not hardcode prices in application code.
|
|
153
|
+
|
|
154
|
+
## Callbacks (webhooks)
|
|
155
|
+
|
|
156
|
+
Pass `callback_url` on `create()` (or any `run()` call) and RunAPI will POST the payload to you:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
await client.textToMusic.create({
|
|
160
|
+
custom_mode: false,
|
|
161
|
+
instrumental: false,
|
|
162
|
+
prompt: '...',
|
|
163
|
+
model: 'V5',
|
|
164
|
+
callback_url: 'https://your.app/webhooks/runapi/suno',
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Suno fires the callback at multiple stages — `generation_stage` tells you which:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
{
|
|
172
|
+
id: string;
|
|
173
|
+
status: 'processing' | 'completed' | 'failed';
|
|
174
|
+
generation_stage: 'text_generated' | 'first_audio_ready' | 'all_audios_ready' | 'failed';
|
|
175
|
+
audios?: Audio[];
|
|
176
|
+
error?: string;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Treat `all_audios_ready` as the terminal success stage. **Always verify the signature before trusting the body.** RunAPI signs every callback with your account's Callback Secret (rotate at `/accounts/callback_secret`). Headers:
|
|
181
|
+
|
|
182
|
+
- `X-Callback-Id` — UUID, store to make handler idempotent
|
|
183
|
+
- `X-Callback-Timestamp` — unix seconds, reject if `|now - ts| > 300`
|
|
184
|
+
- `X-Callback-Signature` — base64 HMAC-SHA256 over `` `${id}.${ts}.${rawBody}` `` using the base64-decoded secret
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import crypto from 'node:crypto';
|
|
188
|
+
|
|
189
|
+
function verify(raw: string, id: string, ts: string, sig: string, secret: string) {
|
|
190
|
+
const key = Buffer.from(secret, 'base64');
|
|
191
|
+
const mac = crypto.createHmac('sha256', key)
|
|
192
|
+
.update(`${id}.${ts}.${raw}`)
|
|
193
|
+
.digest('base64');
|
|
194
|
+
return crypto.timingSafeEqual(Buffer.from(mac), Buffer.from(sig));
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Reply `2xx` within 10s; any non-2xx triggers retries.
|
|
199
|
+
|
|
200
|
+
## Errors
|
|
201
|
+
|
|
202
|
+
All errors are re-exported from `@runapi.ai/core`. Always `instanceof` — never string-match messages.
|
|
203
|
+
|
|
204
|
+
| Error | Status | Action |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `AuthenticationError` | 401 | abort; surface "reconnect your API key" |
|
|
207
|
+
| `InsufficientCreditsError` | 402 | prompt user to top up at runapi.ai/billing |
|
|
208
|
+
| `ValidationError` | 400 / 422 | fix params; do not retry |
|
|
209
|
+
| `RateLimitError` | 429 | sleep `err.retryAfterMs`, then retry |
|
|
210
|
+
| `ServiceUnavailableError` | 503 / 455 | retry with backoff; transient service issue |
|
|
211
|
+
| `TaskFailedError` | — | show `err.details` to user; do not auto-retry |
|
|
212
|
+
| `TaskTimeoutError` | — | re-poll with `<resource>.get(id)` |
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import { InsufficientCreditsError, TaskFailedError } from '@runapi.ai/suno';
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
await client.textToMusic.run({ custom_mode: false, instrumental: false, prompt: '...', model: 'V5' });
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (err instanceof InsufficientCreditsError) { /* surface top-up CTA */ }
|
|
221
|
+
else if (err instanceof TaskFailedError) { /* show err.details */ }
|
|
222
|
+
else throw err;
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Gotchas
|
|
227
|
+
|
|
228
|
+
- `model` is required on every call, even endpoints where it looks optional.
|
|
229
|
+
- Custom mode requires `style` **and** `title`. Prompt is required unless `instrumental: true`.
|
|
230
|
+
- `vocal_gender`, `style_weight`, `weirdness_constraint`, and `audio_weight` only apply when advanced settings are on — custom mode, or `default_param_flag: true` on extendMusic.
|
|
231
|
+
- `generateMidi.run()` requires a completed `separateAudioStems` task created with `type: 'split_stem'` — `separate_vocal` does not produce MIDI.
|
|
232
|
+
- `replaceSection` replacement duration should not exceed 50% of the total track.
|
|
233
|
+
- Callbacks fire multiple times; the `all_audios_ready` stage is the final one. Handle every stage idempotently using `X-Callback-Id`.
|
|
234
|
+
- `callback_url` must be reachable from the public internet. `localhost` / `127.0.0.1` URLs will never fire — use a tunnel (cloudflared, ngrok, tailscale funnel) when developing locally.
|
|
235
|
+
|
|
236
|
+
## Dig deeper
|
|
237
|
+
|
|
238
|
+
Package README (full API surface, all params): `node_modules/@runapi.ai/suno/README.md`. Types: `@runapi.ai/suno/dist/types.d.ts`. Product docs: https://runapi.ai/docs.
|