> ## Documentation Index
> Fetch the complete documentation index at: https://docs.audivo.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload your own audio

> Transcribe audio you hold: a recording, or a show with no public feed whose audio you obtained yourself.

Not every show has an RSS feed. Some publish only on Spotify or YouTube, and
some audio is your own. Audivo's servers never fetch from those platforms, but
you can send the file yourself: announce it, `PUT` it to a signed URL, then
quote and confirm it like any episode. The transcript is private to your
account.

You are responsible for holding the rights to have what you upload
transcribed; see the [terms](https://audivo.dev/terms). How you obtain the
file is up to you: a recording, an export from your editor, or a download you
made yourself with a tool such as `yt-dlp`.

## The three calls

<Steps>
  <Step title="Announce the file">
    Tell Audivo the file's SHA-256, its exact length, its content type and how
    long it plays. The duration is what the quote is priced from.

    ```bash theme={null}
    FILE=interview.m4a
    SHA=$(shasum -a 256 "$FILE" | cut -d' ' -f1)   # Linux: sha256sum "$FILE" | cut -d' ' -f1
    BYTES=$(stat -f%z "$FILE")   # Linux: stat -c%s "$FILE"
    DURATION=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$FILE")

    ANNOUNCE=$(curl -sS https://api.audivo.dev/v1/uploads \
      -H "Authorization: Bearer $AUDIVO_API_KEY" \
      -H 'Content-Type: application/json' \
      -d "{\"sha256\":\"$SHA\",\"bytes\":$BYTES,\"content_type\":\"audio/mp4\",\"declared_duration_seconds\":$DURATION,\"title\":\"Interview\"}")
    echo "$ANNOUNCE"
    ```

    Set `content_type` to the file's real type; the accepted values are in
    [Limits](#limits) below.

    The answer is `201` with an `upload_id`, a `put_url`, the `put_headers`
    the `PUT` must carry, `put_url_expires_at` (one hour from now) and
    `retained_until` (seven days from now). If `ffprobe` is not installed,
    declare the duration your player shows, in seconds.
  </Step>

  <Step title="Send the file">
    `PUT` the body to `put_url` with exactly the returned headers and nothing
    else; they are part of the signature. The object store refuses a body
    whose length or hash differs from what you announced.

    ```bash theme={null}
    ARGS=()
    while IFS= read -r header; do ARGS+=(-H "$header"); done \
      < <(jq -r '.put_headers | to_entries[] | "\(.key): \(.value)"' <<<"$ANNOUNCE")

    curl -sS -X PUT "$(jq -r .put_url <<<"$ANNOUNCE")" "${ARGS[@]}" --upload-file "$FILE"
    ```

    A `400` from the object store means the bytes you sent do not match the
    announcement: announce the file as it is now and send it again. The
    signed URL expires after one hour; announce again if it lapses.
  </Step>

  <Step title="Quote and confirm">
    Name the upload in a quote, then confirm as usual.

    ```bash theme={null}
    curl -sS https://api.audivo.dev/v1/quotes \
      -H "Authorization: Bearer $AUDIVO_API_KEY" \
      -H 'Content-Type: application/json' \
      -d "{\"uploads\":[{\"upload_id\":\"$(jq -r .upload_id <<<"$ANNOUNCE")\"}]}"
    ```

    The quote checks the object once (present, the announced length, the
    announced hash) and prices it from the declared duration with the usual
    25% ceiling. An upload that fails the check comes back in `excluded`
    rather than failing the whole quote. Then
    [confirm, poll and read](/submit-and-poll) exactly as for a feed episode.
  </Step>
</Steps>

## What the quote can say

| `excluded[].reason`   | meaning                                                                   | what to do                                 |
| --------------------- | ------------------------------------------------------------------------- | ------------------------------------------ |
| `upload_not_found`    | No such `upload_id` for this account, or it passed `retained_until`.      | Announce and send the file again.          |
| `upload_not_received` | Announced, but no object is in the bucket yet.                            | Finish the `PUT`, then quote again.        |
| `upload_mismatch`     | An object is there, but its length or hash differs from the announcement. | Announce the file as it is and send again. |

Every entry on a quote from uploads carries `upload_id` and reports
`quote_basis: declared`.

## Pricing and the declared duration

An upload is priced from the duration you declared, the same way a feed
episode is priced from its `<itunes:duration>`: `estimated_credits` from the
minutes, a ceiling 25% above it, and settlement at the measured minutes
capped at the ceiling. If the audio runs past the ceiling the declaration
reserved, the job fails as `processing_failed` with the message "The audio
runs past the ceiling the quote reserved for its declared duration", and the
reservation is released. Declare the real duration: rounding up a little is
fine, rounding down is not. [Credits](/credits) explains the four moments.

## Privacy and retention

* The transcript is cached for your account alone. Another account
  uploading the same bytes gets its own transcript at full price, and
  nothing about your upload is visible to it.
* The audio is sent to Audivo's inference sub-processor to be transcribed,
  the same as any episode; see [Your data](/data).
* The file is deleted seven days after announcement. The transcript and the
  job records stay with your account like any other.
* Each account may hold 10 GiB across 100 unexpired uploads at once. An
  announcement counts against this even if no file is sent; the room comes
  back at its `retained_until`. Over it, the announcement is refused as
  [`429 upload_quota_exceeded`](/errors#upload_quota_exceeded) and the
  message names the earliest time enough room returns.
* In the transcript's provenance the show is your private "Uploads" show,
  with the feed URL `audivo://uploads/<your account id>`; the episode title
  is the `title` you announced.

## Limits

| limit                 | value                                                                                                                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| File size             | 1 byte to 5 GiB (5,368,709,120 bytes)                                                                                                                                                             |
| Declared duration     | Above 0, at most 10 hours                                                                                                                                                                         |
| Content types         | `audio/mpeg`, `audio/mp3`, `audio/mp4`, `audio/m4a`, `audio/x-m4a`, `audio/aac`, `audio/x-aac`, `audio/ogg`, `audio/opus`, `audio/flac`, `audio/x-flac`, `audio/wav`, `audio/x-wav`, `audio/webm` |
| Title                 | 1 to 300 characters, optional                                                                                                                                                                     |
| Signed URL validity   | 1 hour                                                                                                                                                                                            |
| Retention             | 7 days from announcement                                                                                                                                                                          |
| Per-account allowance | 10 GiB across 100 unexpired uploads                                                                                                                                                               |

The schemas are in the API Reference under **Uploads**.

## From an MCP client or the skill

The local MCP server has an `upload_audio` tool that does the three calls
for a file on your machine; see
[Transcribe audio you have](/mcp-tools#transcribe-audio-you-have). The
[agent skill](https://github.com/AudivoDotDev/skills) ships
`skills/audivo/scripts/upload.sh` for the same flow from a shell.
