Presigned audio upload

Request a direct upload URL for a podcast:

POST /podcasts/:podcast_id/uploads
Content-Type: application/json

{"filename":"episode.mp3","content_type":"audio/mpeg"}
{
  "upload_id": "SIGNED_UPLOAD_ID",
  "upload_url": "https://storage.example/...",
  "method": "PUT",
  "headers": { "Content-Type": "audio/mpeg" },
  "expires_in": 900
}

Accepted content_type values:

Formatcontent_type
MP3audio/mpeg
M4Aaudio/mp4, audio/x-m4a, or audio/m4a
AACaudio/aac
WAVaudio/wav or audio/x-wav

Any other type returns 422 upload_invalid with the message “Unsupported audio type.” Uploads can be up to 500 MB. Use the returned method, upload_url, and every returned header for the direct upload. The upload URL lasts 15 minutes. The upload_id lasts 24 hours and is valid only for the podcast that requested it.

Create or update a draft with the signed ID:

{
  "episode": {
    "title": "Field notes",
    "audio_upload_id": "SIGNED_UPLOAD_ID"
  }
}

When you attach the upload, the API checks its size and file header. An invalid, missing, or oversized upload, or a file that doesn’t match its declared type, returns 422 upload_invalid.

Conversion to MP3

M4A, AAC, and WAV uploads are converted to MP3 (192 kbps, stereo, 44.1 kHz) in the background after you attach them. The full audio check runs during conversion. While it runs, audio.status is processing. When it finishes, audio.mime_type is audio/mpeg and the stored file is an MP3. Feeds always serve MP3. If conversion fails, audio.status is failed and audio.error explains why.

Import audio from a URL

As an alternative, send audio_url when creating or updating an episode:

{
  "episode": {
    "title": "Field notes",
    "audio_url": "https://media.example.com/episode.mp3"
  }
}

The API accepts a public HTTP or HTTPS URL on its standard port. The importer checks every redirect and the address it connects to, blocks private and special-use IP addresses, limits downloads to 500 MB, and verifies MP3 data. Imports run in the background. URL imports must be MP3; they aren’t converted. Send either audio_upload_id or audio_url, never both.

Audio status

Read the episode while the file is prepared:

GET /podcasts/:podcast_id/episodes/:id

The response has audio.status:

StatusMeaning
noneNo audio is attached.
processingAn upload is being stored or converted, or a URL is being imported.
readyThe audio is stored and the episode can be published.
failedA URL import or an audio conversion failed; inspect audio.error.

The audio object also includes url, duration, mime_type, and size when available. Wait for ready before calling the publish endpoint. Publishing without audio returns invalid; publishing during processing returns audio_not_ready.

Episode artwork

An episode can have its own artwork. Podcast apps show it on that episode in place of the show’s artwork. Set it when creating or updating an episode, in one of two ways.

Upload a file. Send episode[cover] as multipart/form-data:

curl -X PATCH "$TURBOCAST_API/podcasts/PODCAST_HASHID/episodes/EPISODE_HASHID" \
  -H "Authorization: Bearer $TURBOCAST_TOKEN" \
  -F "episode[cover][email protected]"

Fetch from a URL. Send cover_url in a JSON episode body. The API applies the same public-address and redirect checks used for remote audio.

Either way, the image must be JPEG or PNG and at most 20 MB. Apple Podcasts expects square artwork between 1400 × 1400 and 3000 × 3000 pixels. An invalid image returns 422 upload_invalid with param set to cover or cover_url.

To go back to the show’s artwork, send remove_cover:

{ "episode": { "remove_cover": true } }