Official Go SDK for music recognition API: identify music from a short audio clip, a long audio file, or a live stream.
The API itself is so simple that it can easily be used even without an SDK: docs.audd.io.
go get github.com/AudDMusic/audd-goGet your API token at dashboard.audd.io.
Identify a song hosted at a URL:
package main
import (
"fmt"
"log"
audd "github.com/AudDMusic/audd-go"
)
func main() {
client := audd.NewClient("your-api-token")
defer client.Close()
result, err := client.Recognize("https://audd.tech/example.mp3", nil)
if err != nil {
log.Fatal(err)
}
if result == nil {
fmt.Println("no match")
return
}
fmt.Printf("%s — %s\n", result.Artist, result.Title)
}Identify a song from a local file path:
result, err := client.Recognize("/path/to/clip.mp3", nil)Recognize accepts a URL string, a file-path string, []byte, or any
io.Reader. For longer audio files, use
client.RecognizeEnterprise(source, &audd.EnterpriseOptions{Limit: ptr(1)}),
which returns []EnterpriseMatch across the file's chunks. Each match
carries the same core tags plus Score, StartSeconds, EndSeconds,
StartOffset, EndOffset, ISRC, UPC. Access to ISRC, UPC, and
Score requires a Startup plan or higher — contact us
for enterprise features.
StartSeconds and EndSeconds (*float64) tell you where the song plays
in your file, in seconds — the match's position measured from the start of
the upload. They are precise because the SDK requests accurate offsets by
default; set AccurateOffsets to a non-nil false to opt out. StartOffset
and EndOffset sit behind them as the raw fragment-relative milliseconds.
StartSeconds/EndSeconds are nil when a chunk arrives without a usable
offset.
Every method has an explicit-context twin: RecognizeContext(ctx, source, opts),
Streams().AddContext(ctx, req), Advanced().FindLyricsContext(ctx, query),
and so on. The plain form is the default for short scripts; the
*Context form is what you reach for in servers and pipelines that need
cancellation, deadlines, or a context-propagated tracing span.
Requires Go 1.21+.
Pass your token to NewClient:
client := audd.NewClient("your-api-token")Or leave it empty and the SDK reads the AUDD_API_TOKEN environment variable:
// AUDD_API_TOKEN=your-token ...
client := audd.NewClient("")Get a real token at dashboard.audd.io. The
public "test" token works for the snippets above but is capped at 10
requests.
For long-running services that pull tokens from a secret manager and need to swap them without restarting:
if err := client.SetAPIToken(newToken); err != nil {
log.Fatal(err)
}In-flight requests continue with the previous token; subsequent ones use the new value.
If you'd rather fail fast at construction time when no token is configured,
use NewClientStrict, which returns audd.ErrMissingAPIToken.
By default Recognize returns the core tags plus AudD's universal song link —
no metadata-block opt-in needed:
result, err := client.Recognize("https://audd.tech/example.mp3", nil)
if err != nil { log.Fatal(err) }
if result == nil { return } // no match
fmt.Println(result.Artist, "—", result.Title)
fmt.Println("Album: ", result.Album)
fmt.Println("Released: ", result.ReleaseDate)
fmt.Println("Label: ", result.Label)
fmt.Println("AudD song: ", result.SongLink) // links into every provider
// Helpers, driven off SongLink — work without any ReturnMetadata opt-in:
fmt.Println("Cover art: ", result.ThumbnailURL())
fmt.Println("On Spotify: ", result.StreamingURL(audd.ProviderSpotify))
for provider, url := range result.StreamingURLs() {
fmt.Printf(" %s -> %s\n", provider, url)
}If you need provider-specific metadata blocks, opt in per call. Request only what you need — each provider you ask for adds latency:
result, _ := client.Recognize("https://audd.tech/example.mp3", &audd.RecognizeOptions{
ReturnMetadata: "apple_music,spotify",
})
fmt.Println("Apple Music:", result.AppleMusic.URL)
fmt.Println("Spotify URI:", result.Spotify.URI)
fmt.Println("Preview: ", result.PreviewURL()) // first preview across requested providers, "" if noneValid ReturnMetadata values: apple_music, spotify, deezer, napster,
musicbrainz. The metadata-block fields (AppleMusic, Spotify, Deezer,
Napster) are pointers and may be nil; MusicBrainz is a slice — guard
accordingly.
result.Extras is a map[string]json.RawMessage of every server field outside
the typed surface; result.RawResponse is the original JSON object. Use them
to read fields outside the typed surface or beta fields not yet exposed as typed
properties:
if raw, ok := result.Extras["song_length"]; ok {
var seconds int
_ = json.Unmarshal(raw, &seconds)
fmt.Println("song length:", seconds)
}
// Same channel exists on every metadata block:
if raw, ok := result.AppleMusic.Extras["genreNames"]; ok {
var genres []string
_ = json.Unmarshal(raw, &genres)
}For the request side, every options struct has an ExtraParameters map[string]string field that's sent as additional form fields — useful
for parameters the SDK doesn't expose as typed options:
result, _ := client.Recognize(url, &audd.RecognizeOptions{
ReturnMetadata: "apple_music",
ExtraParameters: map[string]string{
"some_beta_flag": "true",
},
})Typed fields win on collision: if the same key appears in both
ExtraParameters and a typed field, the typed value is sent.
Match by category with sentinel errors:
import "errors"
result, err := client.Recognize(source, nil)
switch {
case errors.Is(err, audd.ErrAuthentication):
// 900 / 901 / 903 — token problems
case errors.Is(err, audd.ErrQuota):
// 902 — quota exceeded
case errors.Is(err, audd.ErrInvalidAudio):
// 300 / 400 / 500 — audio is the problem
case errors.Is(err, audd.ErrRateLimit):
// 611 — back off and retry
case errors.Is(err, audd.ErrServer):
// 5xx, non-JSON gateway responses
case err != nil:
log.Println("audd:", err)
}For the underlying numeric code, message, and request ID, extract the typed error:
var apiErr *audd.AudDAPIError
if errors.As(err, &apiErr) {
log.Printf("[#%d] %s (request_id=%s)", apiErr.ErrorCode, apiErr.Message, apiErr.RequestID)
}Sentinels: ErrAuthentication, ErrQuota, ErrSubscription,
ErrCustomCatalogAccess, ErrInvalidRequest, ErrInvalidAudio,
ErrRateLimit, ErrStreamLimit, ErrNotReleased, ErrBlocked,
ErrNeedsUpdate, ErrServer, ErrConnection, ErrSerialization.
client := audd.NewClient("your-api-token",
audd.WithMaxAttempts(5),
audd.WithBackoffFactor(time.Second),
audd.WithStandardTimeout(2*time.Minute),
audd.WithEnterpriseTimeout(2*time.Hour),
audd.WithHTTPClient(&http.Client{ /* corporate proxy, mTLS, etc. */ }),
audd.WithOnEvent(func(e audd.AudDEvent) { /* see Observability */ }),
audd.WithOnDeprecation(func(msg string) {
// Server-side soft-deprecation warnings (code 51) land here.
slog.Warn("audd-deprecation", "msg", msg)
}),
)Retries are cost-aware:
- Read endpoints retry on 408/429/5xx and any net error.
- Recognition endpoints retry only on 5xx and pre-upload connection failures (DNS, TCP dial). Post-upload errors are not retried — the server may have already done the metered work.
- Mutating endpoints retry only on pre-upload connection failures.
WithOnEvent receives a structured AudDEvent for every request /
response / exception on the wire — method/URL/status/elapsed/request_id,
no api_token, no body bytes. It pairs cleanly with the standard library's
log/slog, so every API call shows up as a
structured JSON log record. See
examples/observability_slog for a ~50-line
drop-in.
Stream recognition turns AudD into a continuous monitor for an audio stream (internet radio, Twitch, YouTube live, raw HLS/Icecast) and notifies you for every recognized song. Set up streams once, then either receive matches via a callback URL or poll for them.
streams := client.Streams()
// 1. Tell AudD where to POST recognition results for your account.
streams.SetCallbackUrl("https://your.app/audd/callback", &audd.SetCallbackUrlOptions{
ReturnMetadata: "apple_music,spotify",
})
// 2. Add streams to monitor.
streams.Add(audd.AddStreamRequest{
URL: "https://example.com/radio.m3u8", RadioID: 1,
})
streams.Add(audd.AddStreamRequest{URL: "twitch:somechannel", RadioID: 2})
// 3. Inspect what you have configured.
list, _ := streams.List()
for _, s := range list {
fmt.Println(s.RadioID, s.URL, "running:", s.StreamRunning)
}Inside your callback receiver, parse the POST body into a typed payload:
http.HandleFunc("/audd/callback", func(w http.ResponseWriter, r *http.Request) {
match, notif, err := audd.HandleCallback(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if match != nil {
fmt.Println("matched:", match.Song.Artist, "—", match.Song.Title)
for _, alt := range match.Alternatives {
fmt.Println(" alt:", alt.Artist, "—", alt.Title)
}
}
if notif != nil {
fmt.Println("notification:", notif.NotificationMessage)
}
})HandleCallback(r) reads the body and parses it. Use audd.ParseCallback(body) instead if you already have the bytes (queue consumer, replay tool).
See examples/streams_callback_handler
and examples/streams_setup for runnable code.
If hosting a callback receiver isn't an option, longpoll for events from the
client side. Three typed channels — Matches, Notifications, Errors — drive a
select loop:
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
radioID := 1 // any integer you choose — your handle for this stream
poll, err := streams.LongpollByRadioIDContext(ctx, radioID, nil)
if err != nil { log.Fatal(err) }
defer poll.Close()
for {
select {
case m := <-poll.Matches:
fmt.Println("matched:", m.Song.Artist, "—", m.Song.Title)
case n := <-poll.Notifications:
fmt.Println("notification:", n.NotificationMessage)
case err := <-poll.Errors:
log.Fatal(err)
case <-ctx.Done():
return
}
}For browser widgets, embedded UIs, or anywhere you need to consume a category without leaking the API token, use the tokenless variant — same channels, same select loop:
consumer := audd.NewLongpollConsumer(category)
defer consumer.Close()
poll := consumer.Iterate(nil)
defer poll.Close()
// then the same select loop as aboveaudd.DeriveLongpollCategory(token, radioID) is also available as a package
function for computing categories on a server and shipping them to a frontend
that runs NewLongpollConsumer.
The v0 flat methods (RecognizeByUrl, AddStream, FindLyrics, …) still
work as deprecated wrappers and will be removed in v2.0.0. The current API is
namespaced:
| v0 | v1 |
|---|---|
client.RecognizeByUrl(url, "apple_music", nil) |
client.Recognize(url, &audd.RecognizeOptions{ReturnMetadata: "apple_music"}) |
client.RecognizeByFile(reader, "", nil) |
client.Recognize(reader, nil) |
client.AddStream(url, 7, "before", nil) |
client.Streams().Add(audd.AddStreamRequest{URL: url, RadioID: 7, Callbacks: "before"}) |
client.FindLyrics(query, nil) |
client.Advanced().FindLyrics(query) |
client.SetCallbackUrl(url) |
client.Streams().SetCallbackUrl(url, nil) |
client.GetStreams() |
client.Streams().List() |
client.AddSongToCustomDB(id, src) |
client.CustomCatalog().Add(id, src) |
See examples/migration_v0_to_v1 for a
side-by-side runnable example.
Warning
The custom-catalog endpoint is not music recognition. It adds songs to
your account's private fingerprint database, so AudD's recognition can
later identify your own tracks for your account only. If you intended
to identify music, use client.Recognize (or client.RecognizeEnterprise
for longer audio files) instead.
err := client.CustomCatalog().Add(audioID, "/path/to/track.mp3")Custom-catalog access requires a separate subscription. Contact api@audd.io to get it enabled.
client.Advanced() exposes the typed FindLyrics(query) method plus a
generic raw-method escape hatch for AudD endpoints not yet wrapped on this
SDK:
body, err := client.Advanced().RawRequest("newMethodName", map[string]string{
"param": "value",
})MIT — see LICENSE. Security policy: SECURITY.md.
Bug reports and PRs welcome. For account / API questions, email api@audd.io.