Overview
The Lazybird Speech API lets you list available voices, generate MP3 speech from text or SSML, and create, edit and publish cloned voices.
Base URL:
1https://api.lazybird.app/v2/v1 keeps working for existing integrations. It differs only in GET /voices, which there returns one unpaged list of the first 1,000 library voices (?owner=me for your own).
A machine-readable OpenAPI 3.1 description of every endpoint is at www.lazybird.app/openapi.json.
Authentication
There are two ways to get an API key:
- Create one in your Lazybird Studio settings.
- Request one by email, without opening the studio. This is built for AI agents; see Get an API key without the UI.
Send the key with each request.
Preferred header:
1X-API-Key: YOUR_API_KEYYou can also pass the key as an api_key query parameter when a tool cannot set headers:
1https://api.lazybird.app/v2/voices?api_key=YOUR_API_KEYKeep API keys private. Do not expose them in browser code, mobile apps, public repositories, or client-side logs.
Get an API key without the UI (for agents)
An agent can sign up or log in with just an email address. Lazybird emails a 6-digit code; the agent (or you, reading the code back to it) trades the code for a new API key. If no account exists for the email, a free account is created.
1. Request a code
1POST /auth/request-code1curl -X POST "https://api.lazybird.app/v2/auth/request-code" \2 -H "Content-Type: application/json" \3 -d '{"email":"you@example.com"}'Response (202 Accepted):
1{2 "sent": true,3 "expiresInSeconds": 6004}The response is the same whether or not an account exists for the email. Use a permanent inbox; disposable email domains are rejected with 400. You can request one code per email per minute and five per hour; extra requests get 429 with a Retry-After header.
2. Verify the code
1POST /auth/verify-code| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | The email the code was sent to |
code |
string | Yes | The 6-digit code from the email |
keyName |
string | No | A label for the new key, up to 100 characters |
1curl -X POST "https://api.lazybird.app/v2/auth/verify-code" \2 -H "Content-Type: application/json" \3 -d '{"email":"you@example.com","code":"123456","keyName":"my agent"}'Response:
1{2 "apiKey": "YOUR_API_KEY",3 "userId": "abc123",4 "email": "you@example.com",5 "isNewAccount": true6}Codes expire after 10 minutes and work once. A wrong or expired code returns 400 with {"error": "Invalid or expired code"}; after five wrong attempts the code is cancelled and you need to request a new one. Repeated wrong codes for one email, or too many attempts or new accounts from one network or one email domain, return 429 with a Retry-After header. Company and custom domains can create up to 15 new accounts a day this way. A 429 for too many new accounts leaves the code valid, so you can retry before it expires. Each successful verification creates a new key; existing keys keep working.
Store the key somewhere private, such as an environment variable. Accounts created this way start on the free plan; see Check your account and Upgrade your plan.
Agent skill
When you are using an AI coding agent, install the official Lazybird API skill so the agent has the current API shape, examples, and integration gotchas available in context.
1npx skills add lazybirdai/skills --agent codex --skill lazybird-api1curl -X POST "https://api.lazybird.app/v2/generate-speech" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --output speech.mp3 \5 --data '{6 "voiceId": "tgJ2Hes6DauwTg8kFrRPLN",7 "text": "My name is Elyssia and I am a voice from Lazybird."8 }'The response is an MP3 audio file. Save the response body to a file instead of trying to display it as JSON.
Check service status
1GET /statusReturns the current API health.
Example:
1curl "https://api.lazybird.app/v2/status"Response:
1{2 "status": "ok"3}1GET /meReturns the account the API key belongs to, its plan and credit usage for the current period.
1curl "https://api.lazybird.app/v2/me" \2 -H "X-API-Key: YOUR_API_KEY"Response:
1{2 "userId": "abc123",3 "email": "you@example.com",4 "plan": {5 "tier": "free",6 "name": "Free",7 "isPaid": false8 },9 "credits": {10 "used": 1200,11 "limit": 10000,12 "extraCredits": 0,13 "remaining": 8800,14 "resetsAt": "2026-11-01T09:30:00.000Z"15 }16}plan.tier is one of free, creator, pro, max, or legacy for older plans. remaining is the plan allowance left this period plus any purchased extraCredits. resetsAt is when used goes back to zero; it is omitted for team seats. One credit is one character. The plan allowance is used first; purchased extraCredits never expire.
Upgrade or buy credits
1POST /checkoutCreates a checkout link for a paid plan, or for a one-time pack of extra credits. Open the link in a browser to pay; the plan or credits apply to the account as soon as payment completes, and the same API key keeps working.
| Field | Type | Required | Description |
|---|---|---|---|
plan |
string | For a plan | creator, pro or max |
interval |
string | For a plan | monthly or yearly |
creditsUsd |
number | For credits | Whole dollars to spend on credits, from 10 to 1000 |
Extra credits cost $30 per 1M with no plan, $20 per 1M on Creator and $5 per 1M on Pro or Max, so {"creditsUsd":10} buys 333,333 credits with no plan or 2,000,000 on Pro. They never expire. Without a plan they are for personal use; plans include commercial rights.
1curl -X POST "https://api.lazybird.app/v2/checkout" \2 -H "X-API-Key: YOUR_API_KEY" \3 -H "Content-Type: application/json" \4 -d '{"plan":"creator","interval":"monthly"}'5 6curl -X POST "https://api.lazybird.app/v2/checkout" \7 -H "X-API-Key: YOUR_API_KEY" \8 -H "Content-Type: application/json" \9 -d '{"creditsUsd":10}'Response:
1{2 "checkoutUrl": "https://..."3}See pricing for what each plan includes.
List voices
1GET /voicesReturns the voices you can use with generate-speech, in two sections:
myVoices: your own voices (your clones, and any you have published)library: the public voice library
Example:
1curl "https://api.lazybird.app/v2/voices?language=en&gender=female" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"Query parameters (all optional):
| Parameter | Description |
|---|---|
q |
Search text, matched against voice names, descriptions and labels |
language |
A locale such as en-US, or a language such as en for all of its locales |
gender |
female or male |
labels |
Comma-separated labels; a voice must carry all of them |
sort |
recommended (default, also when searching), name, usage or recent |
limit |
Voices per section, 1 to 100. Default 100. |
section |
myVoices or library: return only that section and page through it (see below) |
cursor |
The nextCursor of the previous page, together with section |
Response (abbreviated):
1{2 "myVoices": [],3 "library": [4 {5 "id": "tgJ2Hes6DauwTg8kFrRPLN",6 "displayName": "Elyssia",7 "description": "YouTube Host",8 "language": "en-US",9 "gender": "female",10 "previewText": "Hey everyone, I'm Elyssia.",11 "avatar": "https://cdn.lazybird.app/..."12 }13 ],14 "totals": {15 "myVoices": 0,16 "library": 58017 }18}totals is the number of matching voices in each section. Use the id value as voiceId when generating speech.
Page through a section
Add section to get one section, limit voices at a time. While more voices match, the response includes nextCursor; send it back as cursor with the same filters and sort to get the next page.
1curl "https://api.lazybird.app/v2/voices?section=library&cursor=NEXT_CURSOR" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"1{2 "myVoices": [],3 "library": [],4 "nextCursor": "eyJ0IjoicmVjb21tZW5kZWQiLCJpZCI6..."5}The last page has no nextCursor. If the voice a cursor points at is removed, the request fails with 400; start again from the first page.
Generate speech
1POST /generate-speechGenerates speech with the selected voice and returns an MP3 audio file.
Headers:
| Header | Required | Value |
|---|---|---|
X-API-Key |
Yes | Your Lazybird API key |
Content-Type |
Yes | application/json |
Body:
| Field | Type | Required | Description |
|---|---|---|---|
voiceId |
string | Yes | Voice ID from /voices |
text |
string | No | Plain text to convert to speech |
ssml |
string | No | SSML content. Use this instead of text when you need pauses or speech markup. |
settings |
object | No | Optional voice settings for this generation |
Provide either text or ssml.
Plain text example
1curl -X POST "https://api.lazybird.app/v2/generate-speech" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --output speech.mp3 \5 --data '{6 "voiceId": "tgJ2Hes6DauwTg8kFrRPLN",7 "text": "My name is Elyssia and I am a voice from Lazybird."8 }'1curl -X POST "https://api.lazybird.app/v2/generate-speech" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --output speech.mp3 \5 --data '{6 "voiceId": "tgJ2Hes6DauwTg8kFrRPLN",7 "ssml": "<speak>My name is Elyssia <break time=\"1s\"/> and I am a voice from Lazybird.</speak>"8 }'1{2 "voiceId": "tgJ2Hes6DauwTg8kFrRPLN",3 "text": "This line is slower and slightly higher pitched.",4 "settings": {5 "style": "sad",6 "speed": 0.7,7 "pitch": 1.2,8 "volume": 1,9 "styleIntensity": 1,10 "locale": "es-ES"11 }12}Settings:
| Field | Type | Description |
|---|---|---|
style |
string | Speaking style or tone, when supported by the selected voice |
speed |
number | Speech speed. 1 is normal speed. Must be greater than 0. |
pitch |
number | Voice pitch. 1 is the default pitch. Must be greater than 0. |
volume |
number | Output volume. 1 is the default volume. Must be 0 or greater (0 is silent). |
styleIntensity |
number | Strength of the selected speaking style |
locale |
string | BCP-47 locale override, such as en-US, es-ES, fr-FR, de-DE, or ja-JP, when supported by the selected voice |
Locale override
Use settings.locale to choose the spoken language or accent for multilingual voices. This is especially important for cloned voices, which can be synthesized in different languages by passing a locale override.
1curl -X POST "https://api.lazybird.app/v2/generate-speech" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --output spanish.mp3 \5 --data '{6 "voiceId": "tgJ2Hes6DauwTg8kFrRPLN",7 "text": "Hola, este audio usa una configuración regional en español.",8 "settings": {9 "locale": "es-ES"10 }11 }'1GET /voices?section=myVoicesReturns only your own voices: private cloned voices and any you have published, paged like the library (limit, cursor).
Example:
1curl "https://api.lazybird.app/v2/voices?section=myVoices" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"Response (abbreviated):
1{2 "myVoices": [3 {4 "id": "abc123",5 "displayName": "Narrator",6 "scope": "user",7 "published": false,8 "mine": true9 }10 ],11 "library": []12}Use the id as voiceId when generating speech. Treat it as opaque. Voices cloned before the unified voice list also accept their legacy pv: id anywhere a voice ID is accepted.
Create a voice
1POST /voicesCreates a private cloned voice from a voice sample. Voice cloning requires a plan with voice cloning access and available voice slots.
Upload the sample file as multipart/form-data:
| Field | Type | Required | Description |
|---|---|---|---|
name |
text | Yes | Display name for the cloned voice |
sample |
file | Yes | Voice sample: MP3, WAV, or OGG, up to 20 MB. Use a clean 10–30 second recording of one speaker. |
1curl -X POST "https://api.lazybird.app/v2/voices" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -F "name=Narrator" \4 -F "sample=@sample.wav"If the sample is already hosted at a publicly reachable URL, send JSON instead:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Display name for the cloned voice |
sampleUrl |
string | Yes | Public URL for the source voice sample |
1curl -X POST "https://api.lazybird.app/v2/voices" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --data '{5 "name": "Narrator",6 "sampleUrl": "https://example.com/sample.wav"7 }'Send either a sample file or a sampleUrl, not both. A successful request returns 201 Created with the new voice:
1{2 "voice": {3 "id": "abc123",4 "displayName": "Narrator",5 "scope": "user",6 "published": false,7 "mine": true8 }9}1POST /voices/designGenerates a new voice from a text description of who is speaking and returns a sample of it speaking text. The description is never spoken. Requires voice cloning access.
| Field | Type | Required | Description |
|---|---|---|---|
description |
string | Yes | Who is speaking: age, gender, pitch, energy, accent, emotion. Up to 500 characters. |
text |
string | Yes | What the sample says. Up to 1000 characters. Each design uses characters equal to the length of text, with a minimum of 50. |
language |
string | No | Locale of text, such as en-US or vi-VN. Detected when omitted. |
seed |
integer | No | 0–2147483647. Omit for a new random voice; reuse a returned seed with the same description and text to reproduce it. |
1curl -X POST "https://api.lazybird.app/v2/voices/design" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --data '{5 "description": "A youthful, energetic, enthusiastic American woman",6 "text": "Hey there! I am so excited to show you what we built this week."7 }'1{2 "sampleUrl": "https://cdn.lazybird.app/users/.../voice-design-....mp3",3 "seed": 1359090529,4 "duration": 4.825}Designing does not create a voice or use a voice slot. To keep a design, pass its sampleUrl to Create a voice:
1curl -X POST "https://api.lazybird.app/v2/voices" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --data '{5 "name": "Energetic Host",6 "sampleUrl": "https://cdn.lazybird.app/users/.../voice-design-....mp3"7 }'Generate speech with a cloned voice
Cloned voices work with the normal speech endpoint. Pass the voice id as voiceId:
1curl -X POST "https://api.lazybird.app/v2/generate-speech" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --output cloned.mp3 \5 --data '{6 "voiceId": "abc123",7 "text": "Este audio usa mi voz clonada en español.",8 "settings": {9 "locale": "es-ES"10 }11 }'1GET /voices/{voiceId}Returns one voice: any voice in the public library, or one of your own private voices.
1curl "https://api.lazybird.app/v2/voices/abc123" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"Response:
1{2 "voice": {3 "id": "abc123",4 "displayName": "Narrator",5 "description": "Deep, calm narrator",6 "labels": ["Narrator", "Male"],7 "language": "en-US",8 "avatar": "https://cdn.lazybird.app/...",9 "previewText": "Hi, I'm a cloned voice. I can say anything you want me to say.",10 "previewUrl": "https://cdn.lazybird.app/....mp3",11 "scope": "user",12 "published": false,13 "mine": true14 }15}1PATCH /voices/{voiceId}Changes the details of one of your own voices. Send only the fields you want to change. Edits to a published voice show in the public library right away.
| Field | Type | Description |
|---|---|---|
name |
string | Display name |
description |
string | Short description shown on the voice card, such as the character or show |
labels |
string[] | Replaces the voice's labels. Allowed: Male, Female, Child, Young Adult, Adult, Elder, Narrator, Audiobook, Podcast, Character, Celebrity, Streamer, Advertising, Anime, ASMR, Customer Service, Documentary, Education, Horror, Kids, Meditation, News, Sleep, Storytelling, Trailer, Calm, Deep, Energetic |
language |
string | Primary language and accent as a locale, such as en-US, en-GB, es-MX or ja-JP. An empty string clears it. |
avatarUrl |
string | Public URL of a PNG, JPEG or WebP image to use as the avatar |
1curl -X PATCH "https://api.lazybird.app/v2/voices/abc123" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --data '{5 "description": "Deep, calm narrator",6 "labels": ["Narrator", "Male"],7 "language": "en-US"8 }'To upload the avatar image from a file instead, send multipart/form-data with the image as avatar (PNG, JPEG or WebP, up to 5 MB). The other fields can go in the same form; send labels comma-separated:
1curl -X PATCH "https://api.lazybird.app/v2/voices/abc123" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -F "avatar=@avatar.png" \4 -F "labels=Narrator,Male"Avatars are cropped to a 200×200 square, so use a square image with the face centred. Send either an avatar file or an avatarUrl, not both. Unknown fields are rejected with 400.
The response is the updated voice, in the same { "voice": { ... } } shape as Get a voice. Changing language clears the voice's preview clip, because the preview line follows the language; render it again afterwards.
Replace a voice sample
1PUT /voices/{voiceId}/sampleClones one of your voices again from a new recording. The voice keeps its ID, name, description, labels, language and avatar, so existing projects and integrations keep working and speak with the new sample from then on. Use it when the original sample had background noise, music or a second speaker.
Upload the new sample as multipart/form-data:
| Field | Type | Required | Description |
|---|---|---|---|
sample |
file | Yes | Voice sample: MP3, WAV, or OGG, up to 20 MB. Use a clean 10–30 second recording of one speaker. |
rightsConfirmed |
boolean | For published voices | Confirms this is your own voice or that the speaker agreed to it being cloned |
1curl -X PUT "https://api.lazybird.app/v2/voices/abc123/sample" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -F "sample=@new-sample.wav"Or send JSON with a publicly reachable sampleUrl instead of the file:
1curl -X PUT "https://api.lazybird.app/v2/voices/abc123/sample" \2 -H "X-API-Key: $LAZYBIRD_API_KEY" \3 -H "Content-Type: application/json" \4 --data '{5 "sampleUrl": "https://example.com/new-sample.wav",6 "rightsConfirmed": true7 }'Replacing a sample counts as one voice creation toward your plan's monthly limit, but does not use another voice slot. A published voice changes for everyone who uses it, so rightsConfirmed is required for those. The response is the updated voice, in the same { "voice": { ... } } shape as Get a voice. The old preview clip is cleared; render it again afterwards.
Publish a voice
1POST /voices/{voiceId}/publishAdds one of your private voices to the public voice library. Anyone can then find it, open its page at https://lazybird.app/voices/{voiceId} and generate speech with it. Give the voice a name, description, labels, language and avatar before publishing; they are what people see in the library.
1curl -X POST "https://api.lazybird.app/v2/voices/abc123/publish" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"The response is the voice, now with "scope": "community" and "published": true.
To take it out of the library again, use POST /voices/{voiceId}/unpublish. The voice stays in your own voices.
Render a voice preview
1POST /voices/{voiceId}/previewReturns the voice's short preview clip, the previewText line spoken in the voice. The first call renders it; later calls return the stored clip. Rendering is charged to the voice owner's credits.
1curl -X POST "https://api.lazybird.app/v2/voices/abc123/preview" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"Response:
1{2 "previewUrl": "https://cdn.lazybird.app/....mp3",3 "duration": 4.24}1DELETE /voices/{voiceId}Deletes one of your own voices. A published voice is also removed from the public voice library.
1curl -X DELETE "https://api.lazybird.app/v2/voices/abc123" \2 -H "X-API-Key: $LAZYBIRD_API_KEY"Response:
1{2 "deleted": true3}Deprecated personal voice endpoints
The /personal-voices endpoints are deprecated. They keep working for existing integrations, but new code should use the /voices endpoints. Deprecated responses include a Deprecation header and a Link header that points to the replacement.
| Deprecated | Use instead |
|---|---|
GET /personal-voices |
GET /voices?section=myVoices |
POST /personal-voices |
POST /voices |
DELETE /personal-voices/{voiceId} |
DELETE /voices/{voiceId} |
POST /personal-voices/{voiceId}/generate-speech |
POST /generate-speech with voiceId |
GET /personal-voices and POST /personal-voices still return the original { id, name, sampleUrl } shape. Their id is the legacy pv: id when the voice has one.
Responses
Successful generate-speech requests return an MP3 file.
1HTTP/1.1 200 OK2Content-Type: audio/mpegErrors return JSON with an error message:
1{2 "error": "Authorization Error: Invalid API key"3}| Status | Meaning |
|---|---|
400 |
Invalid input, such as a missing field or a wrong or expired login code |
401 |
Missing or invalid API key |
422 |
Your plan blocked the request, for example you are out of credits or voice cloning slots; check GET /me |
429 |
Too many requests; wait for the number of seconds in the Retry-After header (also in retryAfterSeconds) |
503 |
The service is temporarily unavailable; try again later |
If a request fails, check that your API key is valid, the voiceId exists, and the request includes either text or ssml.
Testing tips
- Make API requests from a server environment, not browser code.
- Start by calling
/voicesand copying a voice ID from the response. - Save
generate-speechresponses with--output speech.mp3. - Keep SSML valid XML. Escape quotation marks correctly when sending SSML inside JSON.