Suno
SunoMusicClient generates music from a text prompt. It talks to
sunoapi.org, a third-party gateway in front of Suno, not a first-party Suno
API. You get the same MusicGenerator contract as every other capability: pass
a MusicRequest, get back a GenerationResult with the audio bytes.
Music generation is a long job. The client submits the request, polls for the result, and downloads the finished track once it is ready.
Credentials
Get a SUNO_API_KEY from your sunoapi.org dashboard (not from Suno directly).
The client sends it as an Authorization: Bearer header. Requests go to
https://api.sunoapi.org/api/v1.
credentialsFromEnv reads the key for you and returns null when it is absent:
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
final credentials = credentialsFromEnv(ProviderId.suno, Platform.environment);
Generate a track
Give a prompt, wait for the track, write the bytes to a file:
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> main() async {
final credentials = credentialsFromEnv(ProviderId.suno, Platform.environment);
if (credentials == null) {
stderr.writeln('Set SUNO_API_KEY first.');
return;
}
final client = SunoMusicClient(credentials: credentials);
// This makes a real, paid request to sunoapi.org and can take a minute.
final result = await client.generateMusic(
const MusicRequest(prompt: 'upbeat synthwave with a driving bassline'),
);
// ai_abstracted never touches the filesystem; you decide where bytes go.
await File('track.mp3').writeAsBytes(result.bytes);
}
The package stays free of dart:io. Writing the file is your code, so the
dart:io import lives in your app, not in the library.
How it works
generateMusic runs three steps against the gateway:
- Submit the request to
/generateand read the task id from the response. - Poll
/generate/record-infountil the track reports a finished audio url. The default interval is 5 seconds and the default deadline is 5 minutes. - Download the first audio url and return its bytes.
result.metadata.providerJobId holds the Suno task id, which is useful for
correlating a render with the gateway's own logs.
A single generation takes a while, so pass onProgress to drive UI. Suno is a
polling provider, so the stages arrive as queued, then running on each poll,
then downloading, then done. See
the progress guide for the full stage sequence.
More control
MusicRequest carries the options Suno accepts. Set instrumental: true to
skip vocals. title and style steer the output, and model selects a
version on the gateway (the value is passed straight through to sunoapi.org, so
use whatever version strings their docs list):
import 'dart:io';
import 'package:ai_abstracted/ai_abstracted.dart';
Future<void> main() async {
final credentials = credentialsFromEnv(ProviderId.suno, Platform.environment)!;
final client = SunoMusicClient(credentials: credentials);
// Real, paid request.
final result = await client.generateMusic(
const MusicRequest(
prompt: 'a slow lo-fi beat for late-night studying',
instrumental: true,
title: 'Midnight Desk',
style: 'lo-fi hip hop',
model: 'V4', // whatever sunoapi.org accepts
),
onProgress: (progress) => stdout.writeln('stage: ${progress.stage.name}'),
);
stdout.writeln('suno task: ${result.metadata.providerJobId}');
await File('midnight_desk.mp3').writeAsBytes(result.bytes);
}
Music jobs can outrun the 5-minute default on busy days. Raise the deadline (and slow the polling if you like) on the constructor:
final client = SunoMusicClient(
credentials: credentials,
pollInterval: const Duration(seconds: 10),
pollTimeout: const Duration(minutes: 8),
);
MusicRequest.seconds and MusicRequest.format are not sent to this
gateway; Suno controls track length, and the mime type comes from the
downloaded file. seed is not sent either, though it is echoed back on
result.seedUsed.
Errors
AiResponseExceptionwhen the submit response has no task id, or is otherwise malformed.AiTimeoutExceptionwhen no finished audio url appears beforepollTimeout. A track that never completes surfaces here, not as a response error.- Auth, rate-limit, and transient HTTP failures come back as
AiAuthException,AiRateLimitException, andAiTransientExceptionfrom the shared transport; transient ones are retried per yourRetryPolicy.
See also
- ElevenLabs for speech and sound effects, the other audio provider.
- Progress and long jobs for the polling stage sequence.