Music Generation
Generate music with Google Lyria 3 through the synchronous /v1/music API
Music Generation
osmAPI generates music with Google Lyria 3 through a dedicated,
synchronous /v1/music API. Unlike video, music returns in a single
round-trip: you POST a prompt and get back either a hosted URL (when object
storage is configured) or the audio bytes inline (otherwise / when the
organization opts out of retention). No polling, no job IDs.
Music generation is currently Google Lyria 3 only. It is a separate flow from chat — music models do not appear in the chat model selector. Try it in the playground at /music.
Available models
You can find all music models on the models page.
| Model ID | Notes | Price (flat) |
|---|---|---|
lyria-3-clip-preview | Short-form (~30s MP3). Text-only prompt. | $0.05 |
lyria-3-pro-preview | Long-form (~2–3 min). MP3. Up to 10 reference images. | $0.08 |
Capabilities by model
| Capability | Lyria 3 Clip | Lyria 3 Pro |
|---|---|---|
| Text-to-music | ✅ | ✅ |
| Reference images | — | ≤ 10 |
| MP3 output | ✅ | ✅ |
| WAV output | — | —¹ |
| Instrumental toggle | ✅ | ✅ |
| Typical duration | ~30 s | ~2–3 min |
| Pricing | $0.05 / song | $0.08 / song |
¹ WAV output is documented by Google for Lyria 3 Pro but is not currently
available through /v1/music — the raw :generateContent endpoint rejects
the documented request shape. All output is MP3 for now.
Lifecycle
One HTTP request — no polling, no separate job. Lyria's :generateContent
endpoint returns the audio bytes inline; osmAPI optionally persists them to
object storage and hands you a download URL.
Generate
curl -X POST "https://api.osmapi.com/v1/music" \
-H "Authorization: Bearer $OSM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "lyria-3-clip-preview",
"prompt": "Upbeat lo-fi hip hop, 85 BPM, instrumental, mellow piano"
}'Returns 200 OK. The response always includes audio.base64 so you can
play/save the track immediately. When object storage is configured (and the
org isn't set to none retention) it also includes a durable audio.url
for later replay (see Storage & retention):
S3-backed response (default — object storage configured, retention retain):
{
"id": "req_…",
"object": "music.generation",
"model": "lyria-3-clip-preview",
"used_model": "lyria-3-clip-preview",
"used_provider": "google-ai-studio",
"audio": {
"base64": "<MP3 bytes as base64>",
"url": "/v1/music/req_…/content",
"mime_type": "audio/mpeg",
"storage": "s3",
"expires_in_days": 30
},
"text": "<instrumental> ...",
"usage": { "cost_usd": 0.05, "cost_inr": 4.9 },
"created": 1748505600
}Why both? The billing log row is written asynchronously, so the
audio.url proxy can 404 for a few seconds right after generation — decode
audio.base64 for instant playback, and use audio.url for durable replay
(e.g. in history). The audio.url is a gateway-proxied path; append it to
https://api.osmapi.com and GET it with your bearer token:
curl -L "https://api.osmapi.com/v1/music/req_…/content" \
-H "Authorization: Bearer $OSM_API_KEY" -o song.mp3The gateway proxy-streams the file from the private storage bucket (the
bucket is never exposed publicly). Range requests are supported, so <audio>
tags seek smoothly.
Inline-only response (when object storage isn't configured, or the org's
retention is none):
{
"id": "req_…",
"object": "music.generation",
"audio": {
"base64": "<MP3 bytes as base64>",
"mime_type": "audio/mpeg",
"storage": "inline"
},
"text": "<instrumental> ...",
"usage": { "cost_usd": 0.05, "cost_inr": 4.9 },
"created": 1748505600
}storage is "inline" for opt-in retention configs without S3, and
"none" for orgs that have explicitly disabled retention — same bytes
either way, just a different label.
Save the audio
For S3-backed responses:
curl -L "https://api.osmapi.com/v1/music/$ID/content" \
-H "Authorization: Bearer $OSM_API_KEY" -o song.mp3For inline responses:
curl -X POST "https://api.osmapi.com/v1/music" \
-H "Authorization: Bearer $OSM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"lyria-3-clip-preview","prompt":"…"}' \
| jq -r '.audio.base64' | base64 -d > song.mp3Or in TypeScript, branching on the response shape:
import fs from "fs";
const r = await fetch("https://api.osmapi.com/v1/music", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OSM_API_KEY}`,
},
body: JSON.stringify({
model: "lyria-3-clip-preview",
prompt: "Upbeat lo-fi hip hop, 85 BPM, instrumental",
}),
});
const data = await r.json();
if (data.audio.url) {
// S3-backed — fetch the file via the gateway proxy
const audio = await fetch(`https://api.osmapi.com${data.audio.url}`, {
headers: { Authorization: `Bearer ${process.env.OSM_API_KEY}` },
});
fs.writeFileSync("song.mp3", Buffer.from(await audio.arrayBuffer()));
} else {
// Inline — decode the base64 directly
fs.writeFileSync("song.mp3", Buffer.from(data.audio.base64, "base64"));
}Pro: reference images
Lyria 3 Pro accepts up to 10 reference images as inline data (Gemini chat-content shape) and composes music inspired by them.
import fs from "fs";
const r = await fetch("https://api.osmapi.com/v1/music", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OSM_API_KEY}`,
},
body: JSON.stringify({
model: "lyria-3-pro-preview",
prompt: "Cinematic strings building to a triumphant brass finale, 100 BPM",
instrumental: true,
reference_images: [
"data:image/jpeg;base64,<base64 image bytes>",
// up to 10
],
}),
});
const data = await r.json();
console.log("Lyrics / structure:", data.text);
// Fetch via the proxied URL if S3-backed; else decode inline.Request fields
| Field | Type | Required | Notes |
|---|---|---|---|
model | string | ✅ | One of the IDs above. |
prompt | string (≤4096 chars) | ✅ | Describe genre, mood, instruments, tempo, structure. |
response_format | "mp3" | — | Currently MP3 only (see note above on WAV). |
instrumental | boolean | — | Appends "Instrumental only, no vocals." to the prompt. |
lyrics | string | — | Custom lyrics, appended as a Lyrics: block. Use [Verse]/[Chorus] tags. Ignored when instrumental is true. |
reference_images | array of data URLs or refs | — | Pro only, up to 10. JPEG/PNG, ≤8MB each. |
Response fields
| Field | Notes |
|---|---|
id | osmAPI request ID — surfaces in the dashboard logs. |
audio.base64 | Base64-encoded MP3 bytes. Always present — decode for instant playback. |
audio.url | Gateway proxy path for durable replay. Present only when storage="s3". Append to base URL and GET with bearer. |
audio.mime_type | "audio/mpeg" (MP3). |
audio.storage | "s3" (hosted), "inline" (returned bytes), or "none" (no copy kept anywhere). |
audio.expires_in_days | Days until the hosted file is auto-deleted. Only on storage="s3". |
text | Lyrics and <structure> markers (e.g. <intro>, <chorus>). |
usage.cost_usd | The flat upstream price for this generation. |
usage.cost_inr | What you were charged in INR (USD × current rate). |
Storage & retention
- Default: when S3 is configured (it is, in osmAPI prod) and your org's
retention is the standard
retainmode, every generated track is uploaded to object storage. The response carriesaudio.urland the file lives for 30 days, then a background worker deletes it. retention=none(org-level setting): the gateway never uploads the bytes. The response carriesaudio.base64andstorage:"none". Nothing is persisted; if your client doesn't save the bytes immediately, they're gone.- No S3 configured: same response shape as
retention=none— inline base64 withstorage:"inline".
The retention sweep runs every ~5 minutes. It deletes the S3 object first,
then marks the log row's mediaCleanedUp flag — so a failed delete is
retried, and crashed workers never leave orphan objects.
Media deletion and chat-log retention are independent. Deleting the
audio file (ENABLE_MEDIA_CLEANUP) does not delete the prompt or lyrics
text from your logs — that's a separate switch (ENABLE_DATA_RETENTION_CLEANUP,
off by default, keeps history forever). So after 30 days a track's audio is
gone but its prompt still shows in history with an "expired" badge.
List recent tracks (history)
GET /v1/music returns the most recent generations for the bearer's
organization. Tracks whose media has been auto-deleted come back with
audio: null and expired: true.
curl "https://api.osmapi.com/v1/music?limit=30" \
-H "Authorization: Bearer $OSM_API_KEY"{
"object": "list",
"data": [
{
"id": "req_…",
"object": "music.generation",
"prompt": "Upbeat lo-fi hip hop…",
"created": 1748505600,
"audio": {
"url": "/v1/music/req_…/content",
"mime_type": "audio/mpeg",
"storage": "s3"
},
"usage": { "cost_inr": 4.9 },
"metadata": {
"used_model": "lyria-3-clip-preview",
"used_provider": "google-ai-studio",
"response_format": "mp3"
},
"expired": false,
"error": false
}
]
}Query params:
limit— max 100, default 30.
The history endpoint sets Cache-Control: no-store so newly generated
tracks always appear on the next fetch.
Download by id
GET /v1/music/:id/content is the canonical download endpoint. The :id
can be either the log row id or the requestId returned by POST — both
resolve. Returns:
302redirect to the durable storage URL when the file is still hosted410 Gonewhen the track has been retention-swept404when the track isn't yours or doesn't exist
Prompt tips (from Google)
- Name the genre, mood, and instruments.
- Add tempo (e.g.
90 BPM) and structure tags (intro / verse / chorus / outro). - Use the
instrumentalflag (or write "instrumental only, no vocals") to skip vocals. - Pro: attach reference images for visual inspiration — Lyria reads mood and color.
- Don't name real artists or copyrighted lyrics — the safety filter blocks these.
Errors
| Code | When |
|---|---|
400 | Missing/invalid model or prompt, image too large, format wrong. |
402 | Insufficient credits and no provider key configured. |
410 | Track has been retention-swept (older than the 30-day window). |
422 | Blocked by Lyria's safety / RAI filter. You are not charged. |
429 | Provider rate-limited the request. |
502 | Upstream provider error. |
504 | Upstream timed out (we wait up to 4 minutes). |
Music generation is not idempotent. osmAPI calls Lyria exactly once per
request — there are no automatic retries on 5xx, so a failed network
round-trip never double-charges. If you build a retry loop on the client,
deduplicate on your side or accept the duplicate cost.
Pricing
Music is billed per generation (not per second or per token). The current
rates are visible on each model's page and in the /v1/models response under
pricing.audio_music_per_generation (USD). osmAPI converts that to INR at the
platform rate before charging your credit balance — same as every other
modality.
Where it lives
- API:
POST /v1/music,GET /v1/music,GET /v1/music/:id/content - Playground: playground.osmapi.com/music — chat-style composer with reference-image attach (Pro), instrumental + custom-lyrics options, and a scrollable history of recent tracks.
- Dashboard nav: Music Studio (under Video Studio)
- Usage analytics: a Music category appears in the cost-by-modality chart as soon as you make your first request.
How is this guide?