# Lazybird Speech and Video API Base URL: https://api.lazybird.app/v2 Lazybird converts text or SSML into MP3 speech. The public API also lets authenticated users create, edit, publish, delete, and synthesize with their own cloned voices, and make whole voiceover and video projects: write the script, add pictures, video and music, render MP3s or MP4s, and share them. ## Critical Rules for Agents - Never expose Lazybird API keys in browser code, mobile apps, public repos, or logs. - Prefer the `X-API-Key` header. Use `api_key` only when a client cannot set headers. - `POST /generate-speech` returns MP3 bytes, not JSON. - Save speech responses to a file or binary buffer. - Use `GET /voices` before synthesis when you need a valid `voiceId`. It is paged: filter or search (`language`, `gender`, `labels`, `q`) instead of downloading the whole library. - Cloned voices are regular voices: list your own with `GET /voices?section=myVoices` and use their `id` as `voiceId` with `POST /generate-speech`. - Create cloned voices with `POST /voices` (multipart `sample` upload or JSON `sampleUrl`). - Edit a voice (name, description, labels, language, avatar) with `PATCH /voices/:voiceId`, then add it to the public library with `POST /voices/:voiceId/publish`. - Re-clone a voice from a better recording, keeping its id, with `PUT /voices/:voiceId/sample`. - Design a new voice from a text description with `POST /voices/design`, then save it with `POST /voices` using the returned `sampleUrl`. - Every voice, cloned ones included, speaks every supported language: set `settings.locale` (a locale with a region, such as `es-ES`) when the text is not in the voice's own language. - Provide exactly one of `text` and `ssml` for speech generation. Unknown fields are rejected with 400. - Keep SSML valid XML and escape quotes correctly inside JSON. - For a voiceover or video longer than one clip, make a project: `POST /projects`, change it with `PATCH /projects/:projectId`, make its speech with `POST /projects/:projectId/speech`, render it with `POST /projects/:projectId/exports` and poll the export until `status` is `completed`. See "Projects and Video". - Renders are queued: start one, then poll. Never start the same export twice. - When a response has `upgradeUrl` anywhere (a failed request or a project's `outOfCredits`), the account ran out of Lazybird credits: tell the user and give them that link to upgrade. ## Authentication No key yet? Get one by email, without the web UI (see "Get an API Key by Email" below). Otherwise the user can create one in Lazybird Studio settings. Send an API key with every request except `/status` and `/auth/*`: X-API-Key: YOUR_API_KEY Fallback query parameter: https://api.lazybird.app/v2/voices?api_key=YOUR_API_KEY ## Endpoints - `GET /status` — health check. - `POST /auth/request-code` — email a 6-digit login code (no key needed). - `POST /auth/verify-code` — trade the code for a new API key; creates a free account if needed. - `GET /me` — the key's account, plan and credit usage. - `POST /checkout` — get a checkout link to upgrade the plan. - `GET /voices` — list voices: your own and the public library, with search, filters and paging. - `GET /voices?section=myVoices` — list your own voices and the clones teammates shared with your team. - `POST /voices` — create a cloned voice from an uploaded sample file or a public sample URL. - `GET /voices/:voiceId` — get one voice (public, or one of your own). - `PATCH /voices/:voiceId` — edit one of your voices: name, description, labels, language, avatar. - `PUT /voices/:voiceId/sample` — replace a voice's sample (multipart `sample` or JSON `sampleUrl`); keeps its id. - `POST /voices/:voiceId/publish` — add one of your voices to the public library. - `POST /voices/:voiceId/unpublish` — take it out of the public library again. - `POST /voices/:voiceId/preview` — get the voice's preview clip URL, rendering it if needed. - `POST /voices/design` — design a new voice from a text description; returns a sample URL. - `DELETE /voices/:voiceId` — delete one of your voices. - `POST /generate-speech` — generate MP3 speech from built-in or cloned voices. - `GET /projects` — list your projects, newest first, with search and paging. - `POST /projects` — create an empty project. - `GET /projects/change-schema` — the JSON Schema of the changes `PATCH /projects/:projectId` takes, and `guide`, how to build the picture and sound. - `GET /projects/:projectId` — read a project: its voiceover, picture, sound and uploads. - `PATCH /projects/:projectId` — change a project: voiceover blocks, pictures, video, audio, animations, canvas. - `DELETE /projects/:projectId` — delete a project. - `POST /projects/:projectId/duplicate` — copy a project. - `POST /projects/:projectId/speech` — make speech for the blocks that need it. - `POST /projects/:projectId/exports` — render a video (MP4/MOV), audio (MP3/WAV/OGG) or transcript file. - `GET /projects/:projectId/exports` — list a project's exports. - `GET /projects/:projectId/exports/:exportId` — one export: its status and, once done, its file URL. - `GET /projects/:projectId/uploads` — list a project's uploaded media. - `POST /projects/:projectId/uploads` — start an upload: returns a signed form to POST the file to. - `POST /projects/:projectId/uploads/:uploadId/finalize` — finish an upload after the file is posted. - `POST /projects/:projectId/uploads/from-url` — copy a stock media or Lazybird file into a project. - `POST /projects/:projectId/share` — get a link anyone can open to watch or listen to the project. - `GET /stock/images`, `GET /stock/videos` — search free stock images and videos to use in projects. - `GET /music` — search stock music to use in projects. OpenAPI 3.1 description: https://www.lazybird.app/openapi.json ## Get an API Key by Email For agents without a key. Ask the user which email to use, request a code, then get the 6-digit code from the user (or their inbox if you have access). curl -X POST "https://api.lazybird.app/v2/auth/request-code" \ -H "Content-Type: application/json" \ -d '{"email":"you@example.com"}' Response (202): `{ "sent": true, "expiresInSeconds": 600 }`. Identical whether or not the account exists. 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 (200): { "apiKey": "...", "userId": "...", "email": "you@example.com", "isNewAccount": true } - `keyName` is optional (max 100 chars). Each success creates a new key. - Codes expire after 10 minutes and work once. - Wrong or expired code: 400 `{ "error": "Invalid or expired code" }`. After 5 wrong attempts the code is cancelled; request a new one. - Use a permanent inbox: disposable email domains get 400. - Limits per inbox (`+tags` and Gmail dots count as the same inbox): 1 code per minute, 5 per hour; 10 wrong codes per hour, 20 per day; 3 new accounts per day. Per IP (generous, for shared networks): 30 codes per hour, 100 per day; 50 wrong codes per hour; 10 new accounts per hour, 30 per day. Per email domain: big providers (Gmail, Outlook, Yahoo, iCloud...) are only capped high; other domains get 20 codes per hour, 50 per day, and 5 new accounts per hour, 15 per day. Over a limit: 429 with `Retry-After` header and `retryAfterSeconds` in the body. A new-account 429 doesn't use up the code; retry within 10 minutes. - 503 means email login is temporarily unavailable; retry later. - Store the key in an environment variable or secret store, never in code. ## Account and Upgrade curl "https://api.lazybird.app/v2/me" -H "X-API-Key: YOUR_API_KEY" Response: { "userId": "...", "email": "you@example.com", "plan": { "tier": "free", "name": "Free", "isPaid": false }, "credits": { "used": 1200, "limit": 3000, "extraCredits": 0, "remaining": 1800, "resetsAt": "2026-11-01T09:30:00.000Z" } } `plan.tier` is `free`, `creator`, `pro`, `max` or `legacy`. `resetsAt` may be absent. When credits run out, offer the user an upgrade: 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"}' Response: `{ "checkoutUrl": "https://..." }`. `plan` is `creator`, `pro` or `max`; `interval` is `monthly` or `yearly`. Give the link to the user to pay in a browser; the same key gets the new plan once payment completes. ## Health Check curl "https://api.lazybird.app/v2/status" Response: { "status": "ok" } ## List Voices curl "https://api.lazybird.app/v2/voices?language=en&gender=female" \ -H "X-API-Key: $LAZYBIRD_API_KEY" Query parameters (all optional): - `q` — search words: each must appear in the voice's name, description or 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; a voice must carry all of them. - `sort` — `recommended` (default), `trending` (most uses in the last 7 days), `usage` (most uses of all time), `recent` (newest first), `name` (A to Z). - `limit` — voices per section, 1-100 (default 100). - `section` — `myVoices` or `library`: return only that section, paged with `cursor`. - `cursor` — `nextCursor` of the previous page; keep the same section, filters and sort. Response shape (abbreviated): { "myVoices": [], "library": [ { "id": "tgJ2Hes6DauwTg8kFrRPLN", "displayName": "Elyssia", "description": "YouTube Host", "language": "en-US", "gender": "female", "previewText": "Hey everyone, I'm Elyssia.", "avatar": "https://..." } ], "totals": { "myVoices": 0, "library": 580 } } `myVoices` holds your own voices (`"mine": true`) and the clones teammates shared with your team; `library` is the public voice library, including any of your voices you have published. `totals` counts every match per section. Both sections follow `sort` exactly, ties ordered by `id`; voices starred in the Studio carry `"favorite": true` but keep their place. With `section`, the response carries `nextCursor` while more voices match (no `totals`); the last page has none. A cursor only works with the sort it came from; a malformed one, one from another sort, or one whose voice was removed fails with 400: start again from the first page. Use the `id` field as `voiceId`. Voice fields (left out when not set): `id`, `displayName`, `description`, `language` (primary locale, e.g. `en-US`), `detectedLanguage` (bare code heard in the sample; `language` wins), `detectedLocale` (accent heard in the sample), `gender` (`female`, `male`, `unknown`), `labels`, `styleList` (values for `settings.style`), `avatar`, `previewText`, `previewUrl`, `scope` (`global`, `community` or `user`), `published`, `mine`, `favorite`, `official`, `featured`, `popular` / `trending` (top 10 of all time / of the last 7 days), `createdAt`, `ownerNickname`, `ownerUserId`, and `stats` with `usageCount`, `usageCount7d`, `popularRank`, `trendingRank`. ## Generate Speech `POST /generate-speech` returns an MP3 file, or a WAV file with `phone`. Request body: { "voiceId": "tgJ2Hes6DauwTg8kFrRPLN", "text": "Hello from Lazybird.", "settings": { "style": "cheerful", "speed": 1, "pitch": 1, "volume": 1, "styleIntensity": 1, "locale": "en-US", "instruction": "warm, upbeat" } } Send exactly one of `text` and `ssml` (not empty). `settings`, each of its fields and `phone` are optional. Any other field, in the body or in `settings`, is rejected with 400. - `speed` and `pitch`: `1` is the default; must be greater than 0. - `volume`: `1` is the default; 0 or greater (0 is silent). - `style`: one of the voice's `styleList` values; ignored, with `styleIntensity`, when the voice does not list it. - `styleIntensity`: strength of `style`; `1` is the default and the closest level the voice has is used. - `locale`: the 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`. Any voice can speak any supported language; without it the voice speaks its own `language`. A bare language such as `es` is rejected with 400. - `instruction`: 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`: 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. `phone` (in the body, next to `settings`): `pcm` or `mulaw` returns an 8 kHz mono WAV for phone systems (voicemail, phone menus) instead of an MP3; `mulaw` is G.711 µ-law. 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": "Hello 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": "Hello from Lazybird." }' Locale override example: 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" } }' ## List Your Voices curl "https://api.lazybird.app/v2/voices?section=myVoices" \ -H "X-API-Key: $LAZYBIRD_API_KEY" Only the `myVoices` section, paged like the library: your private clones, the ones you published (both `"mine": true`), and the clones teammates shared with your team. Abbreviated: { "myVoices": [ { "id": "abc123", "displayName": "Narrator", "scope": "user", "published": false, "mine": true } ], "library": [] } Treat `id` as opaque. Voices cloned before the unified voice list also accept their legacy `pv:` id anywhere a voice ID is accepted. ## Create a Voice Voice cloning creates a private voice owned by the authenticated user. The account must have voice cloning access and available cloned voice slots. Only clone a voice that is the user's own, or whose speaker agreed to it (see the Acceptable Use Policy at https://www.lazybird.app/acceptable-use). Ask the user to confirm before cloning a real person's voice, then send `rightsConfirmed: true` (multipart: `rightsConfirmed=true`). It is recorded on the voice. Upload the sample file as `multipart/form-data`: `name` (text) and `sample` (MP3, WAV, or OGG, up to 20 MB; 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 "rightsConfirmed=true" \ -F "sample=@sample.wav" Or send JSON with a publicly reachable `sampleUrl`: 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", "rightsConfirmed": true }' Send either `sample` or `sampleUrl`, not both. Success is `201 Created`: { "voice": { "id": "abc123", "displayName": "Narrator", "scope": "user", "published": false, "mine": true } } ## Design a Voice Describe who is speaking (age, gender, pitch, energy, accent, emotion) and the voice is generated speaking `text`. The description is never spoken. Fields: `description` (required, up to 500 characters), `text` (required, up to 1000 characters; each design uses characters equal to its length, with a minimum of 50), optional `language` (a locale such as `en-US`; detected from `text` when omitted) and optional `seed` (0-2147483647). Each call without a seed gives a different voice; pass the returned `seed` with the same description and text to reproduce one. 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." }' Response (JSON): { "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, create a voice from its sample (this uses a cloned voice slot): 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 Use the normal speech endpoint with the 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 cloned.mp3 \ --data '{ "voiceId": "abc123", "text": "Este audio usa mi voz clonada en español.", "settings": { "locale": "es-ES" } }' ## Get a Voice curl "https://api.lazybird.app/v2/voices/abc123" \ -H "X-API-Key: $LAZYBIRD_API_KEY" Returns `{ "voice": { ... } }` in the same voice shape as `GET /voices`, with `description`, `labels`, `language`, `avatar`, `previewText` and `previewUrl` when set. Works for any public voice and for your own private voices. ## Edit a Voice Send only the fields to change, as JSON: curl -X PATCH "https://api.lazybird.app/v2/voices/abc123" \ -H "X-API-Key: $LAZYBIRD_API_KEY" \ -H "Content-Type: application/json" \ --data '{ "name": "Narrator", "description": "Deep, calm narrator", "labels": ["Narrator", "Male"], "language": "en-US", "avatarUrl": "https://example.com/avatar.png" }' - `labels` 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` is a locale such as `en-US`, `en-GB`, `es-MX`, `ja-JP`; `""` clears it. Changing it clears the preview clip; render it again after. - `avatarUrl` is a public PNG, JPEG or WebP URL. To upload a local image instead, send multipart with the file as `avatar` (up to 5 MB); text fields can go in the same form, with `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 200x200 square. Unknown fields are rejected with `400`. Returns `{ "voice": { ... } }`. ## Publish a Voice curl -X POST "https://api.lazybird.app/v2/voices/abc123/publish" \ -H "X-API-Key: $LAZYBIRD_API_KEY" Adds your voice to the public library (`scope` becomes `community`, `published` true); its public page is https://www.lazybird.app/voices/abc123. Set name, description, labels, language and avatar first: that is what people see. `POST /voices/:voiceId/unpublish` makes it private again. Both return `{ "voice": { ... } }`. ## Voice Preview Clip curl -X POST "https://api.lazybird.app/v2/voices/abc123/preview" \ -H "X-API-Key: $LAZYBIRD_API_KEY" Returns `{ "previewUrl": "https://...mp3", "duration": 4.2 }`: the voice speaking its `previewText`. The first call renders it (charged to the voice owner's credits); later calls return the stored clip without `duration`. ## Delete a Voice 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's what Lazybird Studio edits; every project you make here opens there too (`projectUrl`). Projects use the account's voice credits for speech, like `POST /generate-speech`. ### Create and read curl -X POST "https://api.lazybird.app/v2/projects" \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"name":"Product launch ad"}' Response (201): `{ "project": { "id": "...", "name": "...", "version": 0, "projectUrl": "...", "createdAt": "...", "updatedAt": "..." } }`. `GET /projects` takes `q`, `limit` (1–100, default 20) and `cursor`, and returns `{ "projects": [...], "total": 12, "nextCursor": "..." }`; `nextCursor` is absent on the last page. A listed project also has `kind` (`audio`, or `video` when it has a picture), `voiceIds` and `firstLine`. `GET /projects/:projectId` returns `{ "id": "...", "project": {...} }`. The project has its `name`, `canvas`, `duration`, `tracks`, the `voiceover` in order, the picture and sound `blocks`, `animations`, `styles`, `variables` and its `uploads`. Each voiceover block has `id`, `voiceId`, `text` and `speech`: `has speech`, `speech being made`, `needs speech`, `speech failed` (with `error`) or `empty` (no text; a new project starts with one empty block). When blocks have no speech because the account ran out of voice credits, the response also has `outOfCredits: { blocks, message, upgradeUrl }`. ### Change a project curl -X PATCH "https://api.lazybird.app/v2/projects/PROJECT_ID" \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"changes":[ {"action":"update_project","project":{"autoGenerate":true}}, {"action":"insert_voiceover","ref":"intro","text":"Meet the fastest way to make a voiceover.","voiceId":"VOICE_ID"}, {"action":"insert_voiceover","after":"intro","pause":0.5}, {"action":"insert_voiceover","text":"Try it free today."} ]}' Changes apply in order, all or none (1–200 per request). `GET /projects/change-schema` returns their full JSON Schema, a description of each action, and `guide`: how to build the picture and sound (the shape of every block, animation, canvas, style, font and variable, with examples). Read `guide` before your first `add_block` or `add_animation`. - `update_project`: name, details, master volume, and `autoGenerate` (make speech after each edit). - `insert_voiceover`, `update_voiceover`, `move_voiceover`, `delete_voiceover`: the voiceover's blocks (text or SSML, voice, voice settings, pauses) and pauses. - `add_block`, `update_block`, `delete_block`: picture and sound elements (text, shapes, SVG), video, images and audio, in the shapes `guide` gives. - `add_animation`, `update_animation`, `delete_animation`; `set_canvas`; `set_style`, `delete_style`; `set_font`, `delete_font`; `set_variable`, `delete_variable`. Give new items a `ref`; later changes in the same request can use it as their `id`. Response: `{ "changed": true, "summary": [...], "ids": { "intro": "..." }, "speechBlockIds": [...], "needSpeechBlockIds": [...] }`. Blocks whose text, voice or settings changed need speech again (`needSpeechBlockIds`), unless `autoGenerate` is on and their speech is already being made (`speechBlockIds`). ### Make speech curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/speech" \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" -d '{}' Response (202): `{ "requested": 4 }`. Optional `blockIds` limits it to those blocks; `regenerate: true` also makes new speech for blocks that have it. Poll `GET /projects/:projectId` until no block is `speech being made`. If the read then has `outOfCredits`, the account ran out of voice credits: give the user its `upgradeUrl`. ### Render (export) curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/exports" \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"type":"video","format":"mp4"}' - `type` `video`: `format` `mp4` or `mov`; optional `width` and `height` (both or neither) to render at another size than the canvas. - `type` `audio`: `format` `mp3`, `wav` or `ogg`. `phone` (`pcm` or `mulaw`) with `wav` renders 8 kHz mono sound for phone systems. - `type` `transcript`: `format` `txt`, `docx`, `srt` or `vtt`. - A video needs a picture (else 400), and every voiceover block needs its speech (else 400; or 422 with `upgradeUrl` when it's missing because voice credits ran out). `allowMissingSpeech: true` renders without the missing speech. Response (202): `{ "export": { "id": "...", "type": "video", "format": "mp4", "status": "exporting", "projectVersion": 7, ... } }`. Poll `GET /projects/:projectId/exports/:exportId` until `status` is `completed` (the file is at `url`) or `failed`. `projectVersion` is the project version it rendered; compare it with the project's `version` to know if it's up to date. Exports share Lazybird's render machines, so each account may render only a few exports at once and a limited length of output a day (seconds of the rendered file; videos count apart from audio, and transcripts count only toward the exports at once). Over either limit: 429 with `Retry-After` and `retryAfterSeconds`. Wait, don't retry in a loop. ### Upload media From a stock result or another Lazybird file (a `cdn.lazybird.app`, Pexels or Pixabay URL; other sites are refused, so upload their files instead): curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/uploads/from-url" \ -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"url":"https://images.pexels.com/photos/3184291/pexels-photo-3184291.jpeg","name":"Team"}' From a local file, in three steps: 1. `POST /projects/:projectId/uploads` with `{"fileName":"clip.mp4","type":"video","size":4812345}` (`type` is `image`, `video` or `audio`; `size` in bytes, up to 100 MB). Response (201): `{ "upload": {...}, "uploadUrl": "https://...", "uploadFields": {...} }`. 2. POST the file as a multipart form to `uploadUrl`, without the API key: each of `uploadFields` as a field, in order, then the file as `file`. The form works for an hour. 3. `POST /projects/:projectId/uploads/:uploadId/finalize`. An upload has `id`, `name`, `type`, `url`, `status` and, for video and audio, `duration`. Use its `url` in `add_block` changes. `GET /stock/images` and `GET /stock/videos` (`q`, `page`) and `GET /music` (`q`, `instrumental`, `minDuration`, `maxDuration` in seconds, `page`, `limit` 1–25) find free media to add the same way. Each stock result has a `block` ready for `add_block`: give it a `startTime`. ### Share curl -X POST "https://api.lazybird.app/v2/projects/PROJECT_ID/share" -H "X-API-Key: YOUR_API_KEY" Response: `{ "url": "https://.../share/..." }`. Anyone with the link can 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 keys A team API key calls only what its scopes allow: `projects:read` (list, read, uploads, exports list, stock and music search), `projects:write` (create, change, delete, duplicate, speech, uploads, share) and `exports` (render). Personal keys can call everything. ## Error Handling - `401` means the API key is missing or invalid. - `400` means the input is invalid: a missing or unknown field, an invalid page cursor, or a wrong/expired login code. - `429` means too many requests: login attempts, project edits and speech requests, or exports at once or per day. Wait `Retry-After` seconds (`retryAfterSeconds` in the body). - `422` means an entitlement, credit, or voice cloning limit blocked the request. When credits ran out, the body has `code` and `upgradeUrl`: give that link to the user so they can upgrade, or get a checkout link with `POST /checkout`. - Speech synthesis success responses use `Content-Type: audio/mpeg`, or `audio/wav` with `phone`. - JSON endpoints return the result object directly. Full human documentation: https://www.lazybird.app/docs/api-reference