Unofficial Swift client for the Freesound API v2 with support for:
- API key auth (
Authorization: Token ...) - OAuth2 bearer auth (
Authorization: Bearer ...) - OAuth2 authorization-code + refresh token exchange
- Typed models for sound metadata + audio descriptor fields
- A
Sendableclient you can share across tasks and actors - Pagination helpers (
nextPage,previousPage,moreResults) - Preview downloads that work without OAuth (
downloadPreview)
- Swift 6 toolchain (Xcode 16+ on Apple platforms)
- macOS 15+, iOS 18+, tvOS 18+, watchOS 11+, visionOS 2+ (Linux and Android via Swift Package Manager)
In Package.swift:
.package(url: "https://github.com/codybrom/FreesoundKit.git", branch: "main")Then add "FreesoundKit" as a dependency to your target.
import FreesoundKit
let client = FreesoundClient(authentication: .apiKey("<YOUR_API_KEY>"))
let page = try await client.textSearch(query: "piano")
print(page.results.first?.name ?? "no results")
// Walk pages without rebuilding query parameters
if let next = try await client.nextPage(of: page) {
print("next page has \(next.results.count) results")
}
// Preview audio is public — no OAuth needed (request the "previews" field)
let sounds = try await client.textSearch(
query: "piano", parameters: ["fields": "id,name,previews"])
if let sound = sounds.results.first {
let mp3Data = try await client.downloadPreview(for: sound)
}import FreesoundKit
let client = FreesoundClient()
// 1. Send user to Freesound authorization page
let authURL = try client.oauthAuthorizationURL(
clientID: "<CLIENT_ID>",
responseState: "session-123"
)
// 2. After redirect, exchange the returned `code` for tokens
let token = try await client.exchangeAuthorizationCode(
clientID: "<CLIENT_ID>",
clientSecret: "<CLIENT_SECRET>",
code: "<AUTHORIZATION_CODE>"
)
// 3. Use OAuth token for protected endpoints
client.authentication = .oauthToken(token.accessToken)
let me = try await client.me()
print(me.username)
// 4. Refresh when needed
let refreshed = try await client.refreshAccessToken(
clientID: "<CLIENT_ID>",
clientSecret: "<CLIENT_SECRET>",
refreshToken: token.refreshToken
)textSearch,contentSearch,combinedSearch(+moreResults)page(at:),nextPage,previousPagefor any paged responsesound,soundAnalysis,similarSounds,soundComments,downloadOriginalSound,downloadPreviewuploadSound,describeSound,editSound,pendingUploads,bookmarkSound,rateSound,commentSounduser,userSounds,userPackspack,packSounds,downloadPackme,myBookmarkCategories,myBookmarkCategorySoundsoauthAuthorizationURL,exchangeAuthorizationCode,refreshAccessToken
All client methods throw FreesoundError, a typed enum:
do {
let page = try await client.textSearch(query: "thunder")
} catch let error as FreesoundError {
switch error {
case .apiError(let statusCode, let detail):
print("API error \(statusCode): \(detail)")
case .rateLimited(let retryAfter, let detail):
print("Throttled (\(detail)); retry after \(retryAfter ?? 60)s")
case .oauthRequired:
print("This endpoint needs an OAuth token")
default:
print(error.localizedDescription)
}
}You can verify the package without creating another project:
swift run freesound-tester helpExamples:
# API key smoke test
FREESOUND_API_KEY=... swift run freesound-tester search --query "piano"
# Build OAuth URL and open it in browser
FREESOUND_CLIENT_ID=... swift run freesound-tester oauth-url --state test
# Exchange returned code for tokens
FREESOUND_CLIENT_ID=... FREESOUND_CLIENT_SECRET=... \
swift run freesound-tester oauth-exchange --code "<CODE>"
# Test protected endpoint
FREESOUND_ACCESS_TOKEN=... swift run freesound-tester me
# Refresh token
FREESOUND_CLIENT_ID=... FREESOUND_CLIENT_SECRET=... FREESOUND_REFRESH_TOKEN=... \
swift run freesound-tester oauth-refreshThis library is an unofficial wrapper. Your use of the Freesound API is governed by Freesound's Terms of Use. A few obligations that affect apps built with this library:
- Non-commercial by default. The API is free for non-commercial use only; commercial use requires a separate licensing agreement with Freesound.
- Attribution. You must credit Freesound and the individual sound authors, and
respect each sound's license (visible via
Sound.license). Surface this wherever you play or display sounds. - One key per app. Don't register multiple API keys to work around limits.
Freesound throttles requests (overview):
| Operation | Per minute | Per day |
|---|---|---|
| Standard (search, fetch, download) | 60 | 2,000 |
| Write (upload, describe, edit, comment, rate, bookmark) | 30 | 500 |
When a limit is exceeded the API responds 429, surfaced here as
FreesoundError.rateLimited(retryAfter:detail:) — retryAfter carries the
server's Retry-After hint in seconds when present, and detail says which
limit was hit. Contact Freesound if you need higher limits.
To honor Retry-After automatically, wrap an idempotent call in
withRateLimitRetry:
let page = try await client.withRateLimitRetry {
try await client.textSearch(query: "rain")
}