Lazybird Platform

API Reference

List Lazybird voices and generate MP3 speech from text or SSML in your own application.

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

Quickstart

Generate speech with a known voice ID:

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"
}

Check your account

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."
  }'

SSML 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",
    "ssml": "<speak>My name is Elyssia <break time=\"1s\"/> and I am a voice from Lazybird.</speak>"
  }'

Voice settings example

All settings fields are optional.

{
  "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"
  }'

List your voices

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
  }
}

Design a voice

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 a voice

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
  }
}

Update a voice

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
}

duration is in seconds and is only included when this call rendered the clip.

Delete a voice

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:

  1. Create a project with POST /projects.
  2. Write the voiceover and add media with PATCH /projects/{projectId}.
  3. Make the speech with POST /projects/{projectId}/speech.
  4. Render with POST /projects/{projectId}/exports and poll the export until it is completed.
  5. 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 /voices and copying a voice ID from the response.
  • Save generate-speech responses with --output speech.mp3.
  • Keep SSML valid XML. Escape quotation marks correctly when sending SSML inside JSON.