Overview
The Lazybird API lets you list available voices, generate MP3 speech from text or SSML, and create, edit and publish cloned voices. It also makes whole voiceover and video projects: write the script, add pictures, video and music, render MP3 or MP4 files and share them. See Projects and video.
Base URL:
https://api.lazybird.app/v2
/v1 is frozen and keeps working for existing integrations. It returns the older voice object, lists voices as one unpaged list of the first 1,000 library voices (?owner=me for your own) and still serves the deprecated /personal-voices endpoints. New integrations should use /v2.
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:
X-API-Key: YOUR_API_KEY
You can also pass the key as an api_key query parameter when a tool cannot set headers:
https://api.lazybird.app/v2/voices?api_key=YOUR_API_KEY
Keep 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
POST /auth/request-code
curl -X POST "https://api.lazybird.app/v2/auth/request-code" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com"}'
Response (202 Accepted):
{
"sent": true,
"expiresInSeconds": 600
}
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
POST /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 |
curl -X POST "https://api.lazybird.app/v2/auth/verify-code" \
-H "Content-Type: application/json" \
-d '{"email":"you@example.com","code":"123456","keyName":"my agent"}'
Response:
{
"apiKey": "YOUR_API_KEY",
"userId": "abc123",
"email": "you@example.com",
"isNewAccount": true
}
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.
npx skills add lazybirdai/skills --agent codex --skill lazybird-api
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output speech.mp3 \
--data '{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"text": "My name is Elyssia and I am a voice from Lazybird."
}'
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
GET /status
Returns the current API health.
Example:
curl "https://api.lazybird.app/v2/status"
Response:
{
"status": "ok"
}
GET /me
Returns the account the API key belongs to, its plan and credit usage for the current period.
curl "https://api.lazybird.app/v2/me" \
-H "X-API-Key: YOUR_API_KEY"
Response:
{
"userId": "abc123",
"email": "you@example.com",
"plan": {
"tier": "free",
"name": "Free",
"isPaid": false
},
"credits": {
"used": 1200,
"limit": 10000,
"extraCredits": 0,
"remaining": 8800,
"resetsAt": "2026-11-01T09:30:00.000Z"
}
}
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
POST /checkout
Creates 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.
curl -X POST "https://api.lazybird.app/v2/checkout" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan":"creator","interval":"monthly"}'
curl -X POST "https://api.lazybird.app/v2/checkout" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"creditsUsd":10}'
Response:
{
"checkoutUrl": "https://..."
}
See pricing for what each plan includes.
List voices
GET /voices
Returns the voices you can use with generate-speech, in two sections:
myVoices: your own voices (your clones, including any you have published) and the clones your teammates shared with your team. Your own carry"mine": true.library: the public voice library, including any of your voices you have published
Example:
curl "https://api.lazybird.app/v2/voices?language=en&gender=female" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Query parameters (all optional):
| Parameter | Description |
|---|---|
q |
Search words; a voice matches when each word appears in its name, description, labels or language name (such as "British English") |
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. Labels come from the fixed set listed under labels in Update a voice |
sort |
recommended (default), trending (most uses in the last 7 days), usage (most uses of all time), recent (newest first) or name (A to Z) |
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):
{
"myVoices": [],
"library": [
{
"id": "tgJ2Hes6DauwTg8kFrRPLN",
"displayName": "Elyssia",
"description": "YouTube Host",
"language": "en-US",
"gender": "female",
"previewText": "Hey everyone, I'm Elyssia.",
"avatar": "https://cdn.lazybird.app/..."
}
],
"totals": {
"myVoices": 0,
"library": 580
}
}
totals is the number of matching voices in each section. Use the id value as voiceId when generating speech.
Both sections follow sort exactly. Voices that tie are ordered by id, so the order stays the same from page to page. Voices you starred in the Studio come back with "favorite": true but keep their place in the order.
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.
curl "https://api.lazybird.app/v2/voices?section=library&cursor=NEXT_CURSOR" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
{
"myVoices": [],
"library": [],
"nextCursor": "eyJ0IjoicmVjb21tZW5kZWQiLCJpZCI6..."
}
The last page has no nextCursor. A cursor only works with the sort it came from. A malformed cursor, one from another sort, or one whose voice was removed fails with 400; start again from the first page.
Voice object
Every voice endpoint returns voices in this shape. Fields without a value are left out.
| Field | Description |
|---|---|
id |
Voice ID. Use it as voiceId and treat it as opaque. |
displayName |
The voice's name |
description |
Short description, such as the character or show |
language |
Primary language and accent as a locale, such as en-US |
detectedLanguage |
Language heard in the voice's sample, as a bare code such as vi. language wins when both are set. |
detectedLocale |
Accent heard in the voice's sample, such as en-GB, when it is clear |
gender |
female, male or unknown |
labels |
Tags such as Narrator or Calm |
styleList |
Speaking styles the voice has for settings.style |
avatar |
Avatar image URL |
previewText |
The line the preview clip speaks |
previewUrl |
Preview clip URL (MP3) |
scope |
global (Lazybird library), community (published by a user) or user (private clone) |
published |
true when the voice is in the public library |
mine |
true when the voice is yours |
favorite |
true when you starred the voice in the Studio |
official |
true for official Lazybird voices |
featured |
true for voices featured in the voice library |
popular |
true for the 10 most used voices of all time |
trending |
true for the 10 most used voices of the last 7 days |
createdAt |
When the voice was added (ISO 8601) |
ownerNickname |
The creator's name, on user-made voices whose creator has a public profile |
ownerUserId |
The creator's user ID, on published voices whose creator has a public profile |
stats.usageCount |
Uses of all time (library voices) |
stats.usageCount7d |
Uses in the last 7 days (library voices) |
stats.popularRank |
Position among the 10 most used voices of all time |
stats.trendingRank |
Position among the 10 most used voices of the last 7 days |
Generate speech
POST /generate-speech
Generates speech with the selected voice and returns an MP3 audio file, or with phone a WAV for phone systems.
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 | One of text and ssml |
Plain text to convert to speech |
ssml |
string | One of text and ssml |
SSML content. Use this instead of text when you need pauses or speech markup. |
settings |
object | No | Optional voice settings for this generation |
phone |
string | No | Return an 8 kHz mono WAV for phone systems instead of an MP3: pcm (16-bit) or mulaw (G.711 µ-law) |
Provide exactly one of text and ssml, and not an empty one. Fields not listed here, in the body or in settings, are rejected with 400.
Plain text example
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output speech.mp3 \
--data '{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"text": "My name is Elyssia and I am a voice from Lazybird."
}'
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output speech.mp3 \
--data '{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"ssml": "<speak>My name is Elyssia <break time=\"1s\"/> and I am a voice from Lazybird.</speak>"
}'
{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"text": "This line is slower and slightly higher pitched.",
"settings": {
"style": "sad",
"speed": 0.7,
"pitch": 1.2,
"volume": 1,
"styleIntensity": 1,
"locale": "es-ES",
"instruction": "tired, sighing"
}
}
Settings:
| Field | Type | Description |
|---|---|---|
style |
string | Speaking style, one of the voice's styleList values such as cheerful. Ignored, together with styleIntensity, when the voice doesn't list it. |
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 style. 1 is the default; the closest level the voice has is used. |
locale |
string | Language and accent to speak in, as a locale with a region such as en-US, es-ES, fr-FR, de-DE or ja-JP. A bare language such as es is rejected with 400. |
instruction |
string | How the speech is acted, in a few words such as whispering, nervous. It is never spoken. Up to 200 characters; brackets are dropped, and an instruction without words is rejected with 400. |
effect |
string | A sound effect on the finished speech: robot, deep, monster, echo (a large hall), radio (a walkie-talkie) or phone (a phone call). Leave it out for plain speech. |
Locale override
Every voice, cloned voices included, can speak every supported language. The voice speaks in its own language unless you pass settings.locale; use it when the text is in another language or you want a different accent.
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output spanish.mp3 \
--data '{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"text": "Hola, este audio usa una configuración regional en español.",
"settings": {
"locale": "es-ES"
}
}'
Phone systems example
For voicemail greetings and phone menus, pass phone to get an 8 kHz mono WAV. Use mulaw when your phone system asks for G.711 µ-law, and pcm when it asks for 16-bit PCM.
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output greeting.wav \
--data '{
"voiceId": "tgJ2Hes6DauwTg8kFrRPLN",
"text": "Thanks for calling. Please leave a message after the tone.",
"phone": "mulaw"
}'
GET /voices?section=myVoices
Returns only the myVoices section, paged like the library (limit, cursor): your private clones, the ones you have published, and the clones your teammates shared with your team. Your own carry "mine": true.
Example:
curl "https://api.lazybird.app/v2/voices?section=myVoices" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Response (abbreviated):
{
"myVoices": [
{
"id": "abc123",
"displayName": "Narrator",
"scope": "user",
"published": false,
"mine": true
}
],
"library": []
}
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
POST /voices
Creates 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. |
curl -X POST "https://api.lazybird.app/v2/voices" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-F "name=Narrator" \
-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 |
curl -X POST "https://api.lazybird.app/v2/voices" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Narrator",
"sampleUrl": "https://example.com/sample.wav"
}'
Send either a sample file or a sampleUrl, not both. A successful request returns 201 Created with the new voice:
{
"voice": {
"id": "abc123",
"displayName": "Narrator",
"scope": "user",
"published": false,
"mine": true
}
}
POST /voices/design
Generates 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. |
curl -X POST "https://api.lazybird.app/v2/voices/design" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"description": "A youthful, energetic, enthusiastic American woman",
"text": "Hey there! I am so excited to show you what we built this week."
}'
{
"sampleUrl": "https://cdn.lazybird.app/users/.../voice-design-....mp3",
"seed": 1359090529,
"duration": 4.82
}
Designing does not create a voice or use a voice slot. To keep a design, pass its sampleUrl to Create a voice:
curl -X POST "https://api.lazybird.app/v2/voices" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"name": "Energetic Host",
"sampleUrl": "https://cdn.lazybird.app/users/.../voice-design-....mp3"
}'
Generate speech with a cloned voice
Cloned voices work with the normal speech endpoint. Pass the voice id as voiceId:
curl -X POST "https://api.lazybird.app/v2/generate-speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--output cloned.mp3 \
--data '{
"voiceId": "abc123",
"text": "Este audio usa mi voz clonada en español.",
"settings": {
"locale": "es-ES"
}
}'
GET /voices/{voiceId}
Returns one voice: any voice in the public library, or one of your own private voices.
curl "https://api.lazybird.app/v2/voices/abc123" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Response:
{
"voice": {
"id": "abc123",
"displayName": "Narrator",
"description": "Deep, calm narrator",
"labels": ["Narrator", "Male"],
"language": "en-US",
"avatar": "https://cdn.lazybird.app/...",
"previewText": "Hi, I'm a cloned voice. I can say anything you want me to say.",
"previewUrl": "https://cdn.lazybird.app/....mp3",
"scope": "user",
"published": false,
"mine": true
}
}
PATCH /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 |
curl -X PATCH "https://api.lazybird.app/v2/voices/abc123" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"description": "Deep, calm narrator",
"labels": ["Narrator", "Male"],
"language": "en-US"
}'
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:
curl -X PATCH "https://api.lazybird.app/v2/voices/abc123" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-F "avatar=@avatar.png" \
-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
PUT /voices/{voiceId}/sample
Clones 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 |
curl -X PUT "https://api.lazybird.app/v2/voices/abc123/sample" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-F "sample=@new-sample.wav"
Or send JSON with a publicly reachable sampleUrl instead of the file:
curl -X PUT "https://api.lazybird.app/v2/voices/abc123/sample" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"sampleUrl": "https://example.com/new-sample.wav",
"rightsConfirmed": true
}'
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
POST /voices/{voiceId}/publish
Adds 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.
curl -X POST "https://api.lazybird.app/v2/voices/abc123/publish" \
-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
POST /voices/{voiceId}/preview
Returns 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.
curl -X POST "https://api.lazybird.app/v2/voices/abc123/preview" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Response:
{
"previewUrl": "https://cdn.lazybird.app/....mp3",
"duration": 4.2
}
DELETE /voices/{voiceId}
Deletes one of your own voices. A published voice is also removed from the public voice library.
curl -X DELETE "https://api.lazybird.app/v2/voices/abc123" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Response:
{
"deleted": true
}
Projects and video
A project is a voiceover script, optionally on a video canvas with pictures, video, music and animations. It is what Lazybird Studio edits, so every project you make through the API opens in the editor too (projectUrl). Speech uses your voice credits, like generate-speech.
The usual flow:
- Create a project with
POST /projects. - Write the voiceover and add media with
PATCH /projects/{projectId}. - Make the speech with
POST /projects/{projectId}/speech. - Render with
POST /projects/{projectId}/exportsand poll the export until it iscompleted. - Get a link anyone can open with
POST /projects/{projectId}/share.
Create, list and read projects
POST /projects
GET /projects
GET /projects/{projectId}
POST /projects/{projectId}/duplicate
DELETE /projects/{projectId}
curl -X POST "https://api.lazybird.app/v2/projects" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Product launch ad"}'
GET /projects takes q, limit (1–100, default 20) and cursor, and returns projects, total and, while more follow, nextCursor. Each project has id, name, kind (audio or video), version, projectUrl and its dates.
GET /projects/{projectId} returns the project's voiceover in order, its picture and sound blocks, animations, canvas, styles, variables and uploads. Each voiceover block has a speech state: has speech, speech being made, needs speech, speech failed or empty (no text; a new project starts with one empty block).
Change a project
PATCH /projects/{projectId}
GET /projects/change-schema
Send a list of changes, applied in order, all or none (up to 200 at once):
curl -X PATCH "https://api.lazybird.app/v2/projects/PROJECT_ID" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"changes": [
{"action": "insert_voiceover", "ref": "intro", "text": "Meet the fastest way to make a voiceover.", "voiceId": "tgJ2Hes6DauwTg8kFrRPLN"},
{"action": "insert_voiceover", "after": "intro", "pause": 0.5},
{"action": "insert_voiceover", "text": "Try it free today."}
]
}'
| Actions | What they change |
|---|---|
update_project |
Name, details, master volume, and autoGenerate (make speech after each edit) |
insert_voiceover, update_voiceover, move_voiceover, delete_voiceover |
Voiceover blocks (text or SSML, voice, voice settings, pauses) and pauses |
add_block, update_block, delete_block |
Elements, video, images and audio |
add_animation, update_animation, delete_animation |
Animations |
set_canvas, set_style, delete_style, set_font, delete_font, set_variable, delete_variable |
The canvas, styles, fonts and variables |
Give a new item a ref, and later changes in the same request can use it as the item's id. The response lists what changed, the ids the refs got, and the blocks that need speech (needSpeechBlockIds). GET /projects/change-schema returns the full JSON Schema of every change and a guide to building the picture and sound: the shape of every block, animation, canvas, style, font and variable, with examples.
Make speech
curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/speech" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{}'
Makes speech for every block that needs it, or only blockIds; regenerate: true also redoes blocks that have speech. Poll GET /projects/{projectId} until no block is speech being made. If the read then has outOfCredits, the account ran out of voice credits, and its upgradeUrl is where to get more.
Render a project
POST /projects/{projectId}/exports
GET /projects/{projectId}/exports
GET /projects/{projectId}/exports/{exportId}
curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/exports" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"type": "video", "format": "mp4"}'
type |
format |
Options |
|---|---|---|
video |
mp4, mov |
width and height to render at another size |
audio |
mp3, wav, ogg |
phone (pcm or mulaw) with wav for phone systems |
transcript |
txt, docx, srt, vtt |
The export starts as exporting. Poll it until status is completed, when url has the file, or failed. The file is kept for 30 days; then the export's status is expired, without a url. projectVersion is the project version it rendered. A video needs a picture, and every block needs its speech unless you pass allowMissingSpeech: true.
Exports share Lazybird's render machines, so each account renders a few exports at a time and a limited length of output per day, counted in seconds of the rendered file, with videos counted apart from audio. A transcript counts toward the exports at a time only. Past either limit the API returns 429 with Retry-After.
Upload media
POST /projects/{projectId}/uploads/from-url
POST /projects/{projectId}/uploads
POST /projects/{projectId}/uploads/{uploadId}/finalize
GET /projects/{projectId}/uploads
Copy a stock result or another Lazybird file (a cdn.lazybird.app, Pexels or Pixabay URL) into the project. Other sites are refused: upload their files instead.
curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/uploads/from-url" \
-H "X-API-Key: $LAZYBIRD_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg", "name": "Team"}'
To upload a local file, call POST /projects/{projectId}/uploads with fileName, type (image, video or audio) and size (bytes). POST the file as a form to the returned uploadUrl, each of uploadFields first, then call finalize:
# uploadFields → one -F per field, in order; the file goes last
curl -X POST "$UPLOAD_URL" -F "key=..." -F "Content-Type=video/mp4" -F "Policy=..." \
-F "X-Amz-Signature=..." ... -F "file=@clip.mp4"
A file is up to 100 MB, and uploads share your plan's storage. On the Free plan, uploads are kept for 30 days. Use an upload's url in add_block changes.
Free stock media to use the same way: GET /stock/images and GET /stock/videos (q, page), and GET /music (q, instrumental, minDuration, maxDuration, page, limit). Each stock result has a block ready for add_block: give it a startTime.
Share a project
curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/share" \
-H "X-API-Key: $LAZYBIRD_API_KEY"
Returns a url anyone can open to watch or listen to the project. It plays the project's latest MP4 (or MP3 for a voiceover) while that export matches the project, otherwise a live preview of the project as it is: export after your last change to share the finished file.
Team API keys
A team API key calls only what its scopes allow: projects:read, projects:write and exports for projects, next to speech, voices:read and voices:write. Personal keys can call everything.
Responses
Successful generate-speech requests return an MP3 file, or a WAV file when you pass phone.
HTTP/1.1 200 OK
Content-Type: audio/mpeg
Errors return JSON with an error message:
{
"error": "Authorization Error: Invalid API key"
}
| Status | Meaning |
|---|---|
400 |
Invalid input, such as a missing or unknown field, an invalid page cursor, 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. When credits ran out, the body has an upgradeUrl |
429 |
Too many requests, or too many exports at once, or too long an output for what's left today; 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 exactly one of text and 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.