@mevdragon/vidfarm-devcli 0.19.1 → 0.20.0

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.
Files changed (34) hide show
  1. package/.agents/skills/music/SKILL.md +416 -0
  2. package/.agents/skills/music/references/api_reference.md +519 -0
  3. package/.agents/skills/music/references/installation.md +65 -0
  4. package/.agents/skills/text-to-speech/SKILL.md +226 -0
  5. package/.agents/skills/text-to-speech/references/installation.md +90 -0
  6. package/.agents/skills/text-to-speech/references/streaming.md +307 -0
  7. package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
  8. package/.agents/skills/vidfarm-media/SKILL.md +25 -11
  9. package/.agents/skills/vidfarm-media/references/tts.md +41 -3
  10. package/SKILL.director.md +31 -13
  11. package/SKILL.platform.md +2 -2
  12. package/dist/src/app.js +129 -41
  13. package/dist/src/cli.js +162 -5
  14. package/dist/src/config.js +12 -0
  15. package/dist/src/devcli/clips.js +7 -2
  16. package/dist/src/domain.js +3 -0
  17. package/dist/src/editor-chat.js +4 -0
  18. package/dist/src/primitive-context.js +14 -0
  19. package/dist/src/primitive-registry.js +140 -18
  20. package/dist/src/reskin/chat-page.js +1 -1
  21. package/dist/src/reskin/inpaint-clipper-page.js +446 -205
  22. package/dist/src/reskin/library-page.js +7 -1
  23. package/dist/src/reskin/portfolio-page.js +9 -9
  24. package/dist/src/reskin/settings-page.js +4 -4
  25. package/dist/src/reskin/theme.js +1 -0
  26. package/dist/src/services/billing.js +5 -0
  27. package/dist/src/services/clip-curation/ffmpeg.js +48 -0
  28. package/dist/src/services/clip-curation/hunt.js +2 -0
  29. package/dist/src/services/clip-curation/index.js +1 -1
  30. package/dist/src/services/clip-curation/scan.js +29 -16
  31. package/dist/src/services/elevenlabs.js +222 -0
  32. package/dist/src/services/providers.js +216 -2
  33. package/dist/src/services/swipe-customize.js +5 -2
  34. package/package.json +1 -1
@@ -0,0 +1,519 @@
1
+ # Music API Reference
2
+
3
+ All endpoints below default to `music_v1` unless noted for backwards compatibility. Always pass `music_v2` unless the caller explicitly needs the legacy model.
4
+
5
+ ## Table of Contents
6
+
7
+ - [compose](#compose)
8
+ - [stream](#stream)
9
+ - [composition_plan.create](#composition_plancreate)
10
+ - [compose_detailed](#compose_detailed)
11
+ - [compose_detailed_stream](#compose_detailed_stream)
12
+ - [upload](#upload)
13
+ - [video_to_music](#video_to_music)
14
+ - [Inpainting](#inpainting)
15
+ - [Error Handling](#error-handling)
16
+
17
+ ## compose
18
+
19
+ Generate music from a text prompt or composition plan. Returns an audio stream.
20
+
21
+ ### Parameters
22
+
23
+ | Parameter | Type | Required | Description |
24
+ |-----------|------|----------|-------------|
25
+ | `prompt` | string | Yes* | Description of desired music |
26
+ | `composition_plan` | object | Yes* | Pre-defined composition plan (alternative to prompt) |
27
+ | `music_length_ms` | integer | No | Duration in milliseconds (3,000–600,000) when using `prompt`; if omitted, the model chooses. Returns an error if a composition plan is also provided. |
28
+ | `model_id` | string | No | Music model. Defaults to `music_v1`. |
29
+ | `force_instrumental` | boolean | No | Guarantee an instrumental output (prompt mode only) |
30
+ | `respect_sections_durations` | boolean | No | Enforce exact `duration_ms` for each composition plan chunk. Ignored if using `music_v2` model. |
31
+ | `output_format` | string | No | Query parameter for output codec/sample-rate/bitrate. `auto` selects `mp3_44100_128` for `music_v1` and `mp3_48000_192` for `music_v2`; high-bitrate MP3 options include `mp3_48000_240` and `mp3_48000_320`. |
32
+
33
+ *Provide either `prompt` or `composition_plan`, not both.
34
+
35
+ ### Python
36
+
37
+ ```python
38
+ audio = client.music.compose(
39
+ prompt="An upbeat electronic track with synth leads",
40
+ music_length_ms=30000,
41
+ model_id="music_v2",
42
+ )
43
+
44
+ with open("output.mp3", "wb") as f:
45
+ for chunk in audio:
46
+ f.write(chunk)
47
+ ```
48
+
49
+ ### TypeScript
50
+
51
+ ```typescript
52
+ const audio = await client.music.compose({
53
+ prompt: "An upbeat electronic track with synth leads",
54
+ musicLengthMs: 30000,
55
+ modelId: "music_v2",
56
+ });
57
+
58
+ const writeStream = createWriteStream("output.mp3");
59
+ audio.pipe(writeStream);
60
+ ```
61
+
62
+ ### With Composition Plan
63
+
64
+ ```python
65
+ plan = client.music.composition_plan.create(
66
+ prompt="A jazz ballad with piano and saxophone",
67
+ music_length_ms=60000,
68
+ model_id="music_v2",
69
+ )
70
+
71
+ # Modify the plan as needed
72
+ audio = client.music.compose(
73
+ composition_plan=plan,
74
+ model_id="music_v2",
75
+ )
76
+ ```
77
+
78
+ ```typescript
79
+ const plan = await client.music.compositionPlan.create({
80
+ prompt: "A jazz ballad with piano and saxophone",
81
+ musicLengthMs: 60000,
82
+ modelId: "music_v2",
83
+ });
84
+
85
+ // Modify the plan as needed
86
+ const audio = await client.music.compose({
87
+ compositionPlan: plan,
88
+ modelId: "music_v2",
89
+ });
90
+ ```
91
+
92
+ ## stream
93
+
94
+ Stream audio chunks as they are generated. Same parameters as [`compose`](#compose); returns an
95
+ iterable/async-iterable of audio bytes instead of a single response. Available on paid plans only.
96
+
97
+ ### Python
98
+
99
+ ```python
100
+ from io import BytesIO
101
+
102
+ stream = client.music.stream(
103
+ prompt="A driving synthwave track with arpeggiated leads",
104
+ music_length_ms=30000,
105
+ model_id="music_v2",
106
+ )
107
+
108
+ buffer = BytesIO()
109
+ for chunk in stream:
110
+ if chunk:
111
+ buffer.write(chunk)
112
+ ```
113
+
114
+ ### TypeScript
115
+
116
+ ```typescript
117
+ const stream = await client.music.stream({
118
+ prompt: "A driving synthwave track with arpeggiated leads",
119
+ musicLengthMs: 30000,
120
+ modelId: "music_v2",
121
+ });
122
+
123
+ const chunks: Buffer[] = [];
124
+ for await (const chunk of stream) {
125
+ chunks.push(chunk);
126
+ }
127
+ ```
128
+
129
+ ## composition_plan.create
130
+
131
+ Generate a structured composition plan from a prompt for granular control before generating audio.
132
+
133
+ ### Parameters
134
+
135
+ | Parameter | Type | Required | Description |
136
+ |-----------|------|----------|-------------|
137
+ | `prompt` | string | Yes | Music description |
138
+ | `music_length_ms` | integer | Yes | Total duration in milliseconds (3,000–600,000) |
139
+ | `model_id` | string | No | Defaults to `music_v2`. Plan structure differs between models. |
140
+
141
+ ### Plan Structure (`music_v2`)
142
+
143
+ A plan is an ordered list of `chunks`. Each chunk is either a **generation chunk** or an
144
+ **audio reference chunk** (see [Inpainting](#inpainting)).
145
+
146
+ | Field | Type | Description |
147
+ |-------|------|-------------|
148
+ | `chunks` | array | Up to 30 chunks; 3 s to 10 min total |
149
+ | `chunks[].text` | string | Section labels (`[Verse 1]`), lyrics, inline cues like `{guitar solo}` |
150
+ | `chunks[].duration_ms` | integer | 3,000–120,000 ms |
151
+ | `chunks[].positive_styles` | array<string> | Up to 50 desired qualities (genre, instrumentation, vocal style). Must be English. |
152
+ | `chunks[].negative_styles` | array<string> | Up to 50 qualities to avoid |
153
+ | `chunks[].context_adherence` | string | `low`, `medium`, or `high` (default). Higher = stays consistent with surrounding chunks. |
154
+
155
+ ### Python
156
+
157
+ ```python
158
+ plan = client.music.composition_plan.create(
159
+ prompt="A peaceful ambient track with nature sounds",
160
+ music_length_ms=60000,
161
+ model_id="music_v2",
162
+ )
163
+
164
+ # Inspect or edit the plan in place
165
+ print(plan["chunks"][0]["positive_styles"])
166
+ plan["chunks"][0]["text"] = "[Intro]\nSoft pad with rain"
167
+
168
+ audio = client.music.compose(composition_plan=plan, model_id="music_v2")
169
+ ```
170
+
171
+ ### TypeScript
172
+
173
+ ```typescript
174
+ const plan = await client.music.compositionPlan.create({
175
+ prompt: "A peaceful ambient track with nature sounds",
176
+ musicLengthMs: 60000,
177
+ modelId: "music_v2",
178
+ });
179
+
180
+ plan.chunks[0].text = "[Intro]\nSoft pad with rain";
181
+ const audio = await client.music.compose({ compositionPlan: plan, modelId: "music_v2" });
182
+ ```
183
+
184
+ ## compose_detailed
185
+
186
+ Generate music while returning both the composition plan and metadata alongside the audio.
187
+
188
+ ### Parameters
189
+
190
+ | Parameter | Type | Required | Description |
191
+ |-----------|------|----------|-------------|
192
+ | `prompt` | string | Yes* | Description of desired music |
193
+ | `composition_plan` | object | Yes* | Pre-defined composition plan (alternative to `prompt`) |
194
+ | `music_length_ms` | integer | No | Total duration in milliseconds when using `prompt` |
195
+ | `model_id` | string | No | Defaults to `music_v2` |
196
+ | `store_for_inpainting` | boolean | No | If `true`, retains the generated audio under a `song_id` so it can be referenced by later inpainting plans |
197
+ | `force_instrumental` | boolean | No | Guarantee an instrumental output (prompt mode only) |
198
+ | `output_format` | string | No | Query parameter for output codec/sample-rate/bitrate. `auto` selects `mp3_44100_128` for `music_v1` and `mp3_48000_192` for `music_v2`; high-bitrate MP3 options include `mp3_48000_240` and `mp3_48000_320`. |
199
+
200
+ *Provide either `prompt` or `composition_plan`, not both.
201
+
202
+ ### Returns
203
+
204
+ | Field | Description |
205
+ |-------|-------------|
206
+ | `json` | Composition plan + song metadata (includes lyrics if applicable) |
207
+ | `filename` | Output file identifier |
208
+ | `audio` | Audio bytes |
209
+ | `song_id` | Identifier for the stored song (only when `store_for_inpainting=True`) |
210
+
211
+ ### Python
212
+
213
+ ```python
214
+ result = client.music.compose_detailed(
215
+ prompt="A pop song about summer adventures",
216
+ music_length_ms=120000,
217
+ model_id="music_v2",
218
+ store_for_inpainting=True,
219
+ )
220
+
221
+ print(result.json) # composition plan + metadata
222
+ print(result.song_id) # reusable identifier for inpainting
223
+
224
+ with open(result.filename, "wb") as f:
225
+ f.write(result.audio)
226
+ ```
227
+
228
+ ### TypeScript
229
+
230
+ ```typescript
231
+ import { writeFileSync } from "fs";
232
+
233
+ const result = await client.music.composeDetailed({
234
+ prompt: "A pop song about summer adventures",
235
+ musicLengthMs: 120000,
236
+ modelId: "music_v2",
237
+ storeForInpainting: true,
238
+ });
239
+
240
+ console.log(result.json); // composition plan + metadata
241
+ console.log(result.songId); // reusable identifier for inpainting
242
+
243
+ writeFileSync(result.filename, result.audio);
244
+ ```
245
+
246
+ ## compose_detailed_stream
247
+
248
+ Stream music generation details as Server-Sent Events from
249
+ [`POST /v1/music/detailed/stream`](https://elevenlabs.io/docs/api-reference/music/compose-detailed-stream).
250
+ Use this when a client should receive the composition plan, song metadata, audio chunks, optional
251
+ word timestamps, and completion signal incrementally.
252
+
253
+ ### Parameters
254
+
255
+ | Parameter | Type | Required | Description |
256
+ |-----------|------|----------|-------------|
257
+ | `prompt` | string | Yes* | Description of desired music |
258
+ | `composition_plan` | object | Yes* | Pre-defined composition plan (alternative to `prompt`) |
259
+ | `music_length_ms` | integer | No | Duration in milliseconds when using `prompt` |
260
+ | `model_id` | string | No | Defaults to `music_v1`; pass `music_v2` for the current model |
261
+ | `seed` | integer | No | Random seed for more consistent generation; cannot be used with `prompt` |
262
+ | `force_instrumental` | boolean | No | Guarantee an instrumental output (prompt mode only) |
263
+ | `store_for_inpainting` | boolean | No | Store the generated song so it can be referenced by later inpainting plans |
264
+ | `with_timestamps` | boolean | No | Include word timestamps in the streamed events |
265
+ | `output_format` | string | No | Query parameter for output codec/sample-rate/bitrate. `auto` selects `mp3_44100_128` for `music_v1` and `mp3_48000_192` for `music_v2`. |
266
+
267
+ *Provide either `prompt` or `composition_plan`, not both.
268
+
269
+ ### cURL
270
+
271
+ ```bash
272
+ curl -N -X POST "https://api.elevenlabs.io/v1/music/detailed/stream?output_format=auto" \
273
+ -H "xi-api-key: $ELEVENLABS_API_KEY" \
274
+ -H "Content-Type: application/json" \
275
+ -d '{"prompt": "A bright indie pop hook with warm guitars", "music_length_ms": 30000, "model_id": "music_v2", "with_timestamps": true}'
276
+ ```
277
+
278
+ ## upload
279
+
280
+ Upload a music file for later inpainting workflows. This endpoint is available to enterprise clients with access to the inpainting feature.
281
+
282
+ ### Parameters
283
+
284
+ | Parameter | Type | Required | Description |
285
+ |-----------|------|----------|-------------|
286
+ | `file` | file | Yes | The audio file to upload |
287
+ | `extract_composition_plan` | boolean \| string | No | If `true` (or a model id such as `"music_v2"`), the response includes an extracted composition plan; passing a model id chooses the extraction model. The request may take longer to return. |
288
+ | `with_timestamps` | boolean | No | Transcribe the uploaded song and include word-level timestamps in the response. The request may take longer to return. |
289
+
290
+ ### Returns
291
+
292
+ | Field | Description |
293
+ |-------|-------------|
294
+ | `song_id` | Unique identifier for the uploaded song |
295
+ | `composition_plan` | Extracted composition plan, or `null` when `extract_composition_plan` is not enabled |
296
+ | `words_timestamps` | Word-level timestamps when `with_timestamps` is enabled |
297
+
298
+ ### Python
299
+
300
+ ```python
301
+ response = client.music.upload(
302
+ file=open("my-song.mp3", "rb"),
303
+ extract_composition_plan="music_v2",
304
+ )
305
+ song_id = response.song_id
306
+ composition_plan = response.composition_plan
307
+ ```
308
+
309
+ ### TypeScript
310
+
311
+ ```typescript
312
+ import { createReadStream } from "fs";
313
+
314
+ const response = await client.music.upload({
315
+ file: createReadStream("my-song.mp3"),
316
+ extractCompositionPlan: "music_v2",
317
+ });
318
+ const songId = response.songId;
319
+ const compositionPlan = response.compositionPlan;
320
+ ```
321
+
322
+ ### cURL
323
+
324
+ ```bash
325
+ curl -X POST "https://api.elevenlabs.io/v1/music/upload" \
326
+ -H "xi-api-key: $ELEVENLABS_API_KEY" \
327
+ -F "file=@<file1>" \
328
+ -F "extract_composition_plan=music_v2"
329
+ ```
330
+
331
+ ## video_to_music
332
+
333
+ Generate background music from uploaded video clips. This is a **separate endpoint** from
334
+ [`compose`](#compose) (`POST /v1/music/video-to-music`, not `POST /v1/music`). See the
335
+ [Video to music API reference](https://elevenlabs.io/docs/api-reference/music/video-to-music).
336
+
337
+ Videos are combined in order before music generation.
338
+
339
+ ### Parameters
340
+
341
+ | Parameter | Type | Required | Description |
342
+ |-----------|------|----------|-------------|
343
+ | `videos` | array of files | Yes | One or more video files. Up to 10 files, 200MB combined size, and 600 seconds total duration. |
344
+ | `description` | string | No | Optional text prompt describing the desired music (up to 1000 characters). |
345
+ | `tags` | array of strings | No | Optional style tags such as `upbeat` or `cinematic` (up to 10 tags). |
346
+ | `model_id` | string | No | Music model to use. Defaults to `music_v1` on this endpoint — pass `music_v2` to opt in. |
347
+ | `sign_with_c2pa` | boolean | No | Sign generated MP3 output with C2PA metadata. Defaults to `false`. |
348
+ | `output_format` | string | No | Output codec/sample-rate/bitrate, such as `mp3_44100_128`, `pcm_44100`, or `opus_48000_96`. |
349
+
350
+ ### Python
351
+
352
+ ```python
353
+ audio = client.music.video_to_music(
354
+ videos=[open("scene-1.mp4", "rb"), open("scene-2.mp4", "rb")],
355
+ description="Cinematic ambient score with a gentle build",
356
+ tags=["cinematic", "ambient"],
357
+ model_id="music_v2",
358
+ )
359
+
360
+ with open("video-score.mp3", "wb") as f:
361
+ for chunk in audio:
362
+ f.write(chunk)
363
+ ```
364
+
365
+ ### TypeScript
366
+
367
+ ```typescript
368
+ import { createReadStream, createWriteStream } from "fs";
369
+
370
+ const audio = await client.music.videoToMusic({
371
+ videos: [createReadStream("scene-1.mp4"), createReadStream("scene-2.mp4")],
372
+ description: "Cinematic ambient score with a gentle build",
373
+ tags: ["cinematic", "ambient"],
374
+ modelId: "music_v2",
375
+ });
376
+
377
+ audio.pipe(createWriteStream("video-score.mp3"));
378
+ ```
379
+
380
+ ### cURL
381
+
382
+ ```bash
383
+ curl -X POST "https://api.elevenlabs.io/v1/music/video-to-music?output_format=mp3_44100_128" \
384
+ -H "xi-api-key: $ELEVENLABS_API_KEY" \
385
+ -F "videos=@scene-1.mp4" \
386
+ -F "videos=@scene-2.mp4" \
387
+ -F "description=Cinematic ambient score with a gentle build" \
388
+ -F "tags=cinematic" \
389
+ -F "tags=ambient" \
390
+ --output video-score.mp3
391
+ ```
392
+
393
+ ## Inpainting
394
+
395
+ Inpainting edits or extends a stored song by combining **audio reference chunks** (unchanged
396
+ slices of stored audio) with **generation chunks** in a single `music_v2` composition plan.
397
+ Available to enterprise clients.
398
+
399
+ ### Getting a `song_id`
400
+
401
+ - Call [`compose_detailed`](#compose_detailed) or [`compose`](#compose) with `store_for_inpainting=True` to keep a
402
+ generation for later editing.
403
+ - Call [`upload`](#upload) with `extract_composition_plan="music_v2"` to import an existing
404
+ track and recover its plan.
405
+
406
+ ### Chunk Types
407
+
408
+ **Audio reference chunk** — replay a slice of a stored song unchanged:
409
+
410
+ | Field | Type | Description |
411
+ |-------|------|-------------|
412
+ | `song_id` | string | Stored song identifier |
413
+ | `range.start_ms` | integer | Start offset (minimum 50 ms range) |
414
+ | `range.end_ms` | integer | End offset |
415
+
416
+ **Generation chunk** — same fields as a normal `music_v2` plan chunk (`text`,
417
+ `duration_ms`, `positive_styles`, `negative_styles`, `context_adherence`), with two optional
418
+ fields for matching the feel of an existing slice:
419
+
420
+ | Field | Type | Description |
421
+ |-------|------|-------------|
422
+ | `conditioning_ref.song_id` | string | Song to condition on |
423
+ | `conditioning_ref.range` | object | `start_ms`/`end_ms`; up to 30,000 ms |
424
+ | `condition_strength` | string | `low`, `medium` (default), `high`, or `xhigh` |
425
+
426
+ `conditioning_ref` on the first chunk influences every subsequent chunk in the plan.
427
+
428
+ ### Constraints
429
+
430
+ | Constraint | Limit |
431
+ |------------|-------|
432
+ | Chunks per plan | 30 |
433
+ | Chunk duration | 3,000–120,000 ms |
434
+ | Conditioning reference duration | up to 30,000 ms |
435
+ | Minimum time range | 50 ms |
436
+
437
+ ### Python
438
+
439
+ ```python
440
+ plan = {
441
+ "chunks": [
442
+ {"song_id": song_id, "range": {"start_ms": 0, "end_ms": 30000}},
443
+ {
444
+ "text": "[Chorus]\nWe're rising up tonight",
445
+ "duration_ms": 30000,
446
+ "positive_styles": ["bigger drums", "layered vocals", "anthemic"],
447
+ "negative_styles": ["sparse"],
448
+ "context_adherence": "high",
449
+ "conditioning_ref": {
450
+ "song_id": song_id,
451
+ "range": {"start_ms": 30000, "end_ms": 45000},
452
+ },
453
+ "condition_strength": "high",
454
+ },
455
+ ]
456
+ }
457
+
458
+ audio = client.music.compose(composition_plan=plan, model_id="music_v2")
459
+ ```
460
+
461
+ ### TypeScript
462
+
463
+ ```typescript
464
+ const plan = {
465
+ chunks: [
466
+ { songId, range: { startMs: 0, endMs: 30000 } },
467
+ {
468
+ text: "[Chorus]\nWe're rising up tonight",
469
+ durationMs: 30000,
470
+ positiveStyles: ["bigger drums", "layered vocals", "anthemic"],
471
+ negativeStyles: ["sparse"],
472
+ contextAdherence: "high",
473
+ conditioningRef: { songId, range: { startMs: 30000, endMs: 45000 } },
474
+ conditionStrength: "high",
475
+ },
476
+ ],
477
+ };
478
+
479
+ const audio = await client.music.compose({ compositionPlan: plan, modelId: "music_v2" });
480
+ ```
481
+
482
+ ## Error Handling
483
+
484
+ ### bad_prompt
485
+
486
+ Occurs when the prompt references copyrighted material (specific artists, bands, or copyrighted lyrics). The error response includes a `prompt_suggestion` with alternative phrasing.
487
+
488
+ ```python
489
+ try:
490
+ audio = client.music.compose(
491
+ prompt="A song like Beatles",
492
+ music_length_ms=30000
493
+ )
494
+ except Exception as e:
495
+ print(f"Request failed: {e}")
496
+ ```
497
+
498
+ ```typescript
499
+ try {
500
+ const audio = await client.music.compose({
501
+ prompt: "A song like Beatles",
502
+ musicLengthMs: 30000,
503
+ });
504
+ } catch (err) {
505
+ console.error("Request failed:", err);
506
+ }
507
+ ```
508
+
509
+ ### bad_composition_plan
510
+
511
+ Returned when a composition plan contains copyrighted styles. The error includes a `composition_plan_suggestion` with corrected styles. No suggestion is provided for harmful content.
512
+
513
+ ### Common HTTP Errors
514
+
515
+ | Code | Meaning |
516
+ |------|---------|
517
+ | 401 | Invalid API key |
518
+ | 422 | Invalid parameters |
519
+ | 429 | Rate limit exceeded |
@@ -0,0 +1,65 @@
1
+ # Installation
2
+
3
+ ## JavaScript / TypeScript
4
+
5
+ ```bash
6
+ npm install @elevenlabs/elevenlabs-js
7
+ ```
8
+
9
+ > **Important:** Always use `@elevenlabs/elevenlabs-js`. The old `elevenlabs` npm package (v1.x) is deprecated and should not be used.
10
+
11
+ ```javascript
12
+ import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
13
+
14
+ // Option 1: Environment variable (recommended)
15
+ // Set ELEVENLABS_API_KEY in your environment
16
+ const client = new ElevenLabsClient();
17
+
18
+ // Option 2: Pass directly
19
+ const client = new ElevenLabsClient({ apiKey: "your-api-key" });
20
+ ```
21
+
22
+ ## Python
23
+
24
+ ```bash
25
+ pip install elevenlabs
26
+ ```
27
+
28
+ ```python
29
+ from elevenlabs import ElevenLabs
30
+
31
+ # Option 1: Environment variable (recommended)
32
+ # Set ELEVENLABS_API_KEY in your environment
33
+ client = ElevenLabs()
34
+
35
+ # Option 2: Pass directly
36
+ client = ElevenLabs(api_key="your-api-key")
37
+ ```
38
+
39
+ ## cURL / REST API
40
+
41
+ Set your API key as an environment variable:
42
+
43
+ ```bash
44
+ export ELEVENLABS_API_KEY="your-api-key"
45
+ ```
46
+
47
+ Include in requests via the `xi-api-key` header:
48
+
49
+ ```bash
50
+ curl -X POST "https://api.elevenlabs.io/v1/music" \
51
+ -H "xi-api-key: $ELEVENLABS_API_KEY" \
52
+ -H "Content-Type: application/json" \
53
+ -d '{"prompt": "A chill lo-fi beat", "music_length_ms": 30000}'
54
+ ```
55
+
56
+ ## Getting an API Key
57
+
58
+ 1. Sign up at [elevenlabs.io](https://elevenlabs.io)
59
+ 2. Go to [API Keys](https://elevenlabs.io/app/settings/api-keys)
60
+ 3. Click **Create API Key**
61
+ 4. Copy and store securely
62
+
63
+ Or use the `setup-api-key` skill for guided setup.
64
+
65
+ **Note:** Music generation requires a paid ElevenLabs plan.