Concept

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

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.

view .md auddretriesidempotencyrate limit

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.

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

Reading this as an AI agent? The raw Markdown is at concepts/retry-strategy.md, and the full index is /resources/llms.txt.