---
title: "Retries: what's safe to retry and what consumes a request"
description: "Which AudD API failures are safe to retry, which consume a request, and how to configure the SDK's retry budget without double-billing or duplicating catalog entries."
slug: "/resources/concepts/retry-strategy"
section: "concepts"
keywords: [audd, retries, idempotency, rate limit, error handling, retry budget]
---

# Retries: what's safe to retry and what consumes a request

A retry is a bet that the same call will work on a second attempt. For a
music-recognition API, that bet is usually right — a recognition call is
idempotent, so retrying a transient blip just costs you a little latency. But
some calls aren't safe to retry blindly, and some failures will never improve
no matter how many times you repeat them. This page sorts AudD's failures into
"retry," "don't retry," and "back off," and explains what the SDKs already do
for you so you don't stack a second retry loop on top.

## TL;DR

- **The official SDKs already retry transient failures** — connection errors
  and 5xx server errors — with exponential backoff, for idempotent
  recognition calls. You usually do **not** add your own retry on top.
- **Recognition calls are safe to retry.** Sending the same audio again
  returns the same kind of result.
- **Cancelling a recognition call mid-flight might still consume a request.**
  Once your bytes are sent, the metered work may already be underway. Don't
  assume a cancelled call was free.
- **Custom-catalog upload (`/upload/`) must not be blindly auto-retried.** A
  naive retry can create duplicate catalog entries. Make upload retries
  idempotent on your side, or check before re-uploading.
- **Rate-limit errors should be backed off, not hammered.** Repeating a
  "too many requests" failure immediately makes it worse.
- The decision rule by error category: retry **connection** and **server**;
  back off **rate-limit**; do not retry **authentication**, **quota**,
  **subscription**, **invalid-request**, or **invalid-audio**.

## Why this matters

Retries exist to paper over the network's bad days: a dropped connection, a
brief server hiccup, a load spike. Applied to those, they turn a flaky
integration into a reliable one. Applied to the wrong failure, they do harm —
they hammer an endpoint that's asking you to slow down, they re-run a bad
request that will always fail, or they create duplicate state.

Two specifics make retries on a recognition API worth thinking about rather
than copy-pasting. First, the SDKs already retry for you, so a hand-rolled
loop on top can multiply attempts (your 3 tries times the SDK's 3 becomes 9)
and turn a polite backoff into a stampede. Second, calls are metered, so a
retry isn't free — and a cancelled call isn't guaranteed free either. Knowing
which failures are worth a second attempt, and which the SDK is already
handling, keeps you from paying twice for nothing or duplicating catalog
entries.

## What the SDKs already do

Every official SDK retries transient failures on idempotent recognition calls
automatically: it catches connection errors and 5xx server responses, waits
with **exponential backoff**, and tries again up to a configured budget before
giving up and raising a typed exception. By the time a connection or server
error reaches your code, the SDK has *already* retried it and exhausted its
budget.

The practical consequence:

> **Don't wrap recognition calls in your own retry loop by default.** The SDK
> already retries connection and server errors with backoff. A second loop on
> top multiplies attempts and can turn a backoff into a stampede. Tune the
> SDK's retry budget instead of adding your own layer.

What the SDK does **not** silently retry for you are the failures that
wouldn't improve on a repeat — authentication, quota, subscription,
invalid-request, invalid-audio — and rate-limit responses, which need a
backoff you may want to control. Those surface to your code as typed
exceptions so you can decide.

## Retryable: connection and server errors

These are transient. The request was well-formed and authorized; something in
the path between you and a result failed temporarily.

- **Connection errors** (`AudDConnectionError`) — DNS failure, dropped socket,
  TLS handshake timeout, the request never reaching the server. The SDK
  retries these.
- **Server errors** (`AudDServerError`, surfacing a 5xx) — the server accepted
  the request but failed to complete it. Transient by nature; the SDK retries
  these too.

Because recognition is idempotent, a retried recognition call returns the same
kind of answer — a match, or `result: null` / an empty list for no match. You
are not at risk of a *different* outcome from retrying; you're only spending
latency and, if the original call had already done metered work, possibly a
request (see below).

## Not retryable: the request itself is the problem

These failures describe a condition that a repeat won't change. Retrying wastes
time and, where the call is metered, money. Fix the cause instead.

- **Authentication** (`AudDAuthenticationError`) — missing, malformed, or wrong
  `api_token`. Every retry fails identically. Fail loudly at startup, not per
  request.
- **Quota** (`AudDQuotaError`) — you've used up your request allowance. More
  attempts won't restore it. Surface to ops; top up or wait for the window to
  reset.
- **Subscription** (`AudDSubscriptionError`) — the feature isn't enabled on
  your plan (for example, calling the enterprise endpoint without enterprise
  access). A retry can't grant access.
- **Invalid request** (`AudDInvalidRequestError`) — a malformed parameter, a
  missing required field. The request is wrong; repeating it sends the same
  wrong request.
- **Invalid audio** (`AudDInvalidAudioError`) — the file wasn't decodable
  audio or video. The bytes won't decode on the second try either. Treat as a
  user/input error: reject the upload with a clear message.

The unifying test: *would the next attempt send something different?* If not,
don't retry — route it to logging, alerting, or a user-facing error.

## Back off, don't hammer: rate-limit errors

A rate-limit error — a 429-style "too many requests" — is its own category. It
is not a permanent failure like authentication, and it is not a free retry
like a connection blip. The server is explicitly telling you to slow down.

The correct response is to **wait and retry with increasing delay** —
exponential backoff, ideally with jitter so a fleet of workers doesn't all
retry in lockstep. Retrying immediately makes the condition worse: you add
load to an endpoint that's already shedding it, and you can extend the
throttling window.

> **Rate limits mean slow down, not stop and not retry-now.** Back off with
> exponential delay and jitter. If you hit rate limits steadily rather than in
> bursts, the fix is fewer concurrent requests or a higher plan — not a
> tighter retry loop.

## Cancellation is not guaranteed free

Cancelling a recognition call in flight — a client timeout, a dropped HTTP
request, a cancelled context — does not guarantee you weren't billed.

Once your audio bytes have reached the server, the metered recognition work
may already be underway. Cancelling your end of the connection stops *you*
waiting; it doesn't necessarily stop the server having done the work. So treat
a cancelled call as **possibly consumed**, not definitely free.

Implications:

- Don't build a "cancel and retry immediately to save money" pattern. The
  cancelled attempt may have cost a request, and the retry costs another.
- Set client timeouts comfortably above the expected response time. The
  standard endpoint responds in under 2 seconds; enterprise calls on long
  files take longer, scaling with how much audio you asked it to process.
  Cutting a slow-but-working call short can pay for work you then throw away.

## Upload is special: never blindly auto-retry

Custom-catalog upload (`POST api.audd.io/upload/`) adds a track to your
account's private fingerprint database. It is **not** idempotent the way
recognition is: a blind retry after an ambiguous failure can create a
**duplicate catalog entry**, which then matches twice on every future
recognition.

The danger case is the ambiguous failure: the upload succeeded server-side but
the response was lost (connection dropped after the work completed). A naive
retry uploads the same track a second time.

Make upload retries safe on your side:

- **Check before re-uploading.** Before retrying, query your catalog for the
  track (by your own external ID or a content hash) and skip the upload if
  it's already there.
- **Carry your own idempotency key.** Track each intended upload by a stable
  ID in your system and mark it done only on a confirmed success, so a worker
  restart doesn't re-submit it.
- **Don't put `/upload/` behind the same generic auto-retry wrapper as
  recognition.** They have different safety properties.

## Worked example: one worker, three failures

A worker pulls jobs off a queue and calls the recognition SDK. Here's how it
should treat the three failure shapes — transient blip, auth failure,
rate-limit — differently. Note that there is no manual retry loop around the
recognition call: the SDK already retried the transient cases before raising.

```python
import time
from audd import AudD
from audd.errors import (
    AudDConnectionError,
    AudDServerError,
    AudDRateLimitError,
    AudDAuthenticationError,
    AudDInvalidAudioError,
    AudDAPIError,
)

audd = AudD("your-api-token")

def handle(job):
    try:
        return audd.recognize(job["audio_url"])

    except (AudDConnectionError, AudDServerError):
        # The SDK already retried with backoff and still failed.
        # Transient — requeue the job to try again later. Do NOT loop here.
        requeue(job, delay_seconds=30)

    except AudDRateLimitError:
        # Back off, don't hammer. Pause this worker, then requeue.
        time.sleep(backoff_with_jitter())
        requeue(job, delay_seconds=0)

    except AudDAuthenticationError:
        # Will never succeed on retry. Stop the worker and page ops —
        # the token is wrong or missing.
        raise

    except AudDInvalidAudioError:
        # The input is bad. Don't retry; mark the job failed for the user.
        mark_failed(job, reason="unreadable_audio")

    except AudDAPIError as e:
        # Quota / subscription / invalid-request land here.
        # None improve on retry. Log error_code + request_id and stop.
        log(e.error_code, e.request_id)
        mark_failed(job, reason="api_error")
```

The shape to notice: **transient → requeue (no inline loop), rate-limit →
back off then requeue, everything else → stop and surface.** The SDK's
internal retries handle the fast in-call recovery; your queue handles the
slower "come back later" recovery; and the never-retryable failures get out of
the retry path entirely.

## Configuring the SDK's retry budget

Rather than adding a retry layer, tune the one the SDK already has. Across the
SDKs the retry budget is configurable at client construction — typically the
**number of attempts** and the **backoff** behavior. Raise the budget for
flaky networks; lower it when you'd rather fail fast and let your queue handle
the wait. Keep it bounded — an unbounded retry budget on a metered, possibly-
already-consumed call is how a transient blip turns into a cost spike.

A reasonable default posture:

- A small, bounded number of attempts (the SDK default is a sensible start).
- Exponential backoff with jitter.
- Your queue, not an inline loop, for the "try again in 30 seconds" tier.

## Common mistakes

- **Stacking your own retry loop on the SDK's.** Multiplies attempts and turns
  backoff into a stampede. Tune the SDK budget instead.
- **Retrying authentication, quota, subscription, or invalid-request.** These
  never improve on a repeat. Detect them and stop.
- **Retrying invalid-audio.** The bytes won't decode the second time. Reject
  the input.
- **Hammering on a rate-limit.** Immediate retries extend the throttle. Back
  off with jitter.
- **Cancel-and-retry to "save" a request.** The cancelled call may already
  have been metered, and the retry costs another. A cancelled call is
  *possibly consumed*, not free.
- **Blindly auto-retrying `/upload/`.** Creates duplicate catalog entries on
  an ambiguous failure. Check first, or carry an idempotency key.
- **Unbounded retries on a metered call.** Always cap the budget.

---

**Related**

- [Standard, enterprise, or streams: how to choose](/resources/concepts/standard-vs-enterprise-vs-streams)
- [Error reference](https://docs.audd.io/reference/errors)
- [Controlling enterprise endpoint cost](/resources/concepts/enterprise-cost-control)
- [API reference](https://docs.audd.io)