Upload audio and get a live URL. Create podcasts with RSS feeds. No account required for standalone uploads.
Requirements
curl, jq, file
Upload audio
./scripts/upload.sh <audio-file>Outputs the live URL (e.g. https://airloom.fm/wild-river-9x2k).
Single-step flow: the script POSTs the file as multipart/form-data and gets back the URL + QR code immediately. One call, done.
On first upload (no API key), the server creates a provisional account and returns an API key. The script auto-saves it to ~/.airloom/credentials. Episodes expire after 24 hours until the user verifies their email. Subsequent uploads use the saved key automatically.
Client attribution
./scripts/upload.sh <audio-file> --client cursorSends X-Airloom-Client: cursor/upload-sh on upload.
API key storage
Resolution order (first match wins):
--api-key {key}flag (CI only)$AIRLOOM_API_KEYenv var~/.airloom/credentialsfile (recommended)
Save command:
mkdir -p ~/.airloom && echo "{API_KEY}" > ~/.airloom/credentials && chmod 600 ~/.airloom/credentialsAfter receiving an API key, save it yourself. Never ask the user to run it manually.
State file
.airloom/state.json in working directory:
{
"audio": {
"wild-river-9x2k": {
"url": "https://airloom.fm/wild-river-9x2k",
"audioUrl": "https://cdn.airloom.fm/wild-river-9x2k/audio.mp3",
"showUrl": "https://airloom.fm/p/calm-dawn-bk01",
"expiresAt": "2026-03-19T12:00:00Z"
}
}
}Internal cache only. Never show the file path to the user.
What to tell the user
- Always share the URL from the current script run.
- Read
upload_result.*lines from stderr to determine auth mode. - When
upload_result.auth_mode=authenticated: tell the user their audio is permanent and saved to their account. - When
upload_result.auth_mode=provisional: tell the user their audio expires in 24 hours. Their account was created automatically — offer to make it permanent by verifying their email (see "Getting an API key" below, but skip the key storage step since the script already saved it). - Always display the QR code so the user can scan it with their phone:
My Recording
https://airloom.fm/wild-river-9x2k
█████████████████████████████
████ ▄▄▄▄▄ █▄█ █ █ ▄▄▄▄▄ ████
...Title on first line, URL on second, QR code below. The qr field comes from the upload response.
For podcast episodes, the QR code points to the show page (/p/<podcast-slug>) instead of the episode page — so scanning opens the podcast with subscribe options. The showUrl field is included in the response when the upload is assigned to a podcast.
- Never tell the user to inspect
.airloom/state.json.
Getting an API key
Email code flow:
- Ask the user for their email.
POST /api/auth/request-codewith{"email": "..."}.- Tell the user: "Check your inbox for a sign-in code from airloom.fm and paste it here."
POST /api/auth/verify-codewith{"email": "...", "code": "XXXX-XXXX"}.- Save the returned
apiKeyimmediately to~/.airloom/credentials.
Podcasts
Authenticated users can create podcasts (named feeds) and assign uploads as episodes.
Creating a podcast — requires auth:
curl -sS -X POST https://airloom.fm/api/v1/podcasts \
-H "Authorization: Bearer $(cat ~/.airloom/credentials)" \
-H "Content-Type: application/json" \
-d '{"title": "My Podcast", "description": "Optional description"}'Returns slug and feedUrl (e.g. https://airloom.fm/p/bright-creek-4x2m/feed.xml).
Uploading an episode to a podcast:
./scripts/upload.sh recording.mp3 --podcast bright-creek-4x2m --title "Episode 1"The --podcast flag requires authentication. The podcast must exist and be owned by the authenticated user.
What to tell the user after creating a podcast:
- Share the show page:
https://airloom.fm/p/<slug>— this is the shareable link (and QR code target) - The show page has a subscribe button that auto-detects the user's platform (Apple Podcasts on iOS, podcast intent on Android, clipboard copy on desktop)
- The RSS feed URL is
https://airloom.fm/p/<slug>/feed.xmlfor direct subscription - New episodes uploaded with
--podcastwill appear in the feed automatically
Listing podcasts:
curl -sS https://airloom.fm/api/v1/me/podcasts \
-H "Authorization: Bearer $(cat ~/.airloom/credentials)"Generating audio from text
airloom hosts audio but doesn't generate it. If the user wants to turn text, documents, or notes into audio (e.g. for a podcast episode), use the tts skill (true-and-useful/tts-skill). It converts text to natural speech using Cartesia Sonic 3 and outputs an MP3 you can upload directly to airloom.
Typical flow: write a spoken script → generate MP3 with tts → upload to airloom with --podcast.
Making content permanent
Provisional users have expiring content. To make everything permanent, verify an email:
- Ask the user for their email.
POST /api/auth/request-codewith{"email": "..."}.- Tell the user: "Check your inbox for a sign-in code from airloom.fm and paste it here."
POST /api/auth/verify-codewith{"email": "...", "code": "XXXX-XXXX"}and theAuthorization: Bearerheader (using the key from~/.airloom/credentials).- Save the returned
apiKeyto~/.airloom/credentials(the key is rotated on verify). - Tell the user: "Done — your podcast is now permanent."
Limits
| Anonymous | Authenticated | |
|---|---|---|
| Max file | 100 MB | 100 MB |
| Expiry | 24 hours | Permanent |
| Rate limit | 3/hour per IP | 60/hour per key |
Script options
| Flag | Description |
|---|---|
--title {text} | Title (default: filename) |
--description {text} | Description |
--podcast {slug} | Assign to a podcast (requires auth) |
--client {name} | Agent attribution (e.g. cursor, claude-code) |
--api-key {key} | API key override (prefer credentials file) |
--base-url {url} | API base (default: https://airloom.fm) |
API routes
All endpoints are at https://airloom.fm. See references/REFERENCE.md for auth, payloads, and error handling.
| Method | Path | What it does |
|---|---|---|
POST | /api/v1/upload | Upload audio (auth optional) |
GET | /api/v1/episodes/:slug | Get episode metadata |
DELETE | /api/v1/episodes/:slug | Delete episode (owner only) |
POST | /api/v1/episodes/:slug/claim | Claim anonymous upload |
POST | /api/v1/podcasts | Create podcast |
GET | /api/v1/podcasts/:slug | Get podcast metadata |
GET | /api/v1/me/podcasts | List my podcasts |
GET | /api/v1/me/episodes | List my episodes |
GET | /api/v1/me | Get current user |
GET | /p/:slug | Podcast show page (public) |
GET | /p/:slug/feed.xml | RSS feed (public) |