Home / Blog / SellerMagnet API Errors: Handling 429, Retry-After and Retries

SellerMagnet API Errors: Handling 429, Retry-After and Retries

Which SellerMagnet API errors cost a credit, which ones are worth retrying, and how to honour Retry-After with exponential backoff in Python and Node.js - built from the status codes the API actually returns.

September 18, 2026
5 min read
SellerMagnet Team
Share & Bookmark
Blueprint sequence diagram of a 429 response, a Retry-After wait and a successful retry

The SellerMagnet API reports failures through the HTTP status and a JSON body with success, error and message. Only three statuses are worth retrying - 429 (too many requests), 502 (upstream error) and 503 (temporarily unavailable) - plus a 401 whose message reads Temporary error. For 429 and 503, wait at least the Retry-After header before trying again; the Python and Node.js code below does both. Everything else is a problem with the request itself.

Key Takeaways

  • Retry only 429, 502 and 503; a 400, 401 or 404 will fail the same way again.
  • Requests rejected before billing - 400, 401, 403 and 429 - never cost a credit.
  • A 404 for a missing product does cost a credit, so record missing ASINs instead of re-requesting them.
  • Always wait at least the number of seconds in the Retry-After header before retrying.
  • Branch on the JSON message as well as the status: an out-of-credits account also answers 401.

What does each SellerMagnet API status code mean?

A status code is the fastest signal, but two of them carry more than one meaning, so the message field decides the reaction. The table reflects the API's behaviour as of September 2026, including whether the request has already been charged.

SellerMagnet API status codes, whether they cost a credit, and whether to retry.
StatusWhen it happensCredit chargedRetry?
400A required parameter is missing or invalid (ASIN, marketplaceId, count above 50)NoNo - fix the request
401api_key is missing or unknown, or the account has no credits left (see message)NoNo - except a Temporary error validating API key
403Credits ran out between the balance check and the chargeNoNo - top up
404The product or seller does not exist in that marketplaceYesNo - record it as missing
429Per-key concurrency cap after a 20-second queue, or the per-IP budgetNoYes - after Retry-After, or back off if it is absent
502An upstream error while fetching dataYesYes - with backoff
503Temporarily unavailable, usually one marketplace's circuit breakerYesYes - after Retry-After
Blueprint table of SellerMagnet API status codes marked billed or not billed and retry or stop
The whole policy on one sheet: three statuses retry, four stop.

What are the SellerMagnet API rate limits?

Two limits apply. Each API key has a cap on concurrent requests; when it is full, the API holds the next request for up to 20 seconds waiting for a free slot, and only answers 429 with Retry-After: 2 if none opens. The 429 message states the current cap. Separately, each client IP has a budget of 600 requests per minute and 15,000 per hour as of September 2026. The per-IP 429 carries no Retry-After and no JSON body, so your client has to fall back to its own backoff.

In practice the concurrency cap is the one you meet first. A burst of scheduled jobs that all fire at the top of the minute queues and drains on its own; a sustained flood beyond the cap is what turns into 429s. The bulk lookup guide shows how to size a worker pool so that never happens.

How do I retry with Retry-After and exponential backoff?

Exponential backoff is a retry policy where each wait doubles: 1, 2, 4, 8 seconds and so on, capped at a maximum. Adding random jitter stops many clients from retrying in lockstep. When the response carries Retry-After, wait at least that long, because the server is telling you exactly when capacity returns.

A retrying GET that honours Retry-After and never retries a billed miss

import os
import random
import time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime

import requests

API = "https://sellermagnet-api.com/api"
RETRYABLE = {429, 502, 503}


def retry_after_seconds(resp) -> float:
    """Retry-After is seconds or an HTTP date (RFC 9110); the per-IP 429 omits it."""
    value = resp.headers.get("Retry-After") if resp is not None else None
    if not value:
        return 0.0
    try:
        return max(0.0, float(value))
    except ValueError:
        try:
            when = parsedate_to_datetime(value)
            return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())
        except (TypeError, ValueError):
            return 0.0


def call(endpoint: str, max_attempts: int = 6, **params) -> dict:
    params["api_key"] = os.environ["SELLERMAGNET_API_KEY"]
    for attempt in range(1, max_attempts + 1):
        try:
            resp = requests.get(f"{API}/{endpoint}", params=params, timeout=60)
        except (requests.ConnectionError, requests.Timeout):
            resp = None  # network blip: treat like a 502
        try:
            body = resp.json() if resp is not None else {}
        except ValueError:
            body = {}  # e.g. a proxy's HTML error page
        status = resp.status_code if resp is not None else 502

        if status == 200 and body.get("success"):
            return body["data"]

        transient_auth = status == 401 and "Temporary error" in body.get("message", "")
        if (status not in RETRYABLE and not transient_auth) or attempt == max_attempts:
            raise RuntimeError(f"{status}: {body.get('message', 'request failed')}")

        backoff = min(2 ** (attempt - 1), 32) + random.uniform(0, 1)
        time.sleep(max(backoff, retry_after_seconds(resp)))


product = call("amazon-product-lookup", asin="B0CLTBHXWQ", marketplaceId="APJ6JRA9NG5V4")
print(product["productInfo"]["title"])

Every retried 502 or 503 is billed again

A 429 is free to retry, but 502 and 503 are charged per attempt. When a 503 arrives, pause every request to that marketplace for the Retry-After period instead of retrying each one - the breaker is per marketplace, so other marketplaces keep working.

Blueprint bar chart of exponential backoff waits doubling from 1 to 32 seconds
The first attempt goes out immediately; each retry waits twice as long as the last.

The same policy in Node.js

The Node.js version uses the built-in fetch from Node 18. For a complete client with search and a worker pool, see the Node.js quickstart.

Node 18+ with the built-in fetch - no dependencies

const API = "https://sellermagnet-api.com/api";
const RETRYABLE = new Set([429, 502, 503]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// Retry-After is seconds or an HTTP date; the per-IP 429 sends none.
function retryAfterSeconds(headers) {
  const value = headers.get("Retry-After");
  if (!value) return 0;
  const seconds = Number(value);
  if (Number.isFinite(seconds)) return Math.max(0, seconds);
  const date = Date.parse(value);
  return Number.isNaN(date) ? 0 : Math.max(0, (date - Date.now()) / 1000);
}

export async function call(endpoint, params, maxAttempts = 6) {
  const url = new URL(`${API}/${endpoint}`);
  for (const [k, v] of Object.entries({ ...params, api_key: process.env.SELLERMAGNET_API_KEY })) {
    url.searchParams.set(k, v);
  }
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    let res, body = {};
    try {
      res = await fetch(url, { signal: AbortSignal.timeout(60_000) });
      body = await res.json().catch(() => ({}));
    } catch {
      res = { status: 502, headers: new Headers() }; // network blip
    }
    if (res.status === 200 && body.success) return body.data;

    const transientAuth = res.status === 401 && /Temporary error/.test(body.message ?? "");
    if ((!RETRYABLE.has(res.status) && !transientAuth) || attempt === maxAttempts) {
      throw new Error(`${res.status}: ${body.message ?? "request failed"}`);
    }
    const backoff = Math.min(2 ** (attempt - 1), 32) + Math.random();
    await sleep(Math.max(backoff, retryAfterSeconds(res.headers)) * 1000);
  }
}

Can I use urllib3's Retry instead?

Yes, for the common cases. Mounting a urllib3 Retry on a requests.Session retries 429, 502 and 503 with exponential backoff and honours Retry-After. It cannot read the JSON message, so it will not retry the transient 401; keep the custom loop above if you need that.

Declarative retries with requests + urllib3

import os

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(
    total=5,
    backoff_factor=1,                  # waits grow as backoff_factor x 2^n seconds
    status_forcelist=[429, 502, 503],  # only the retryable statuses
    allowed_methods=["GET"],
    respect_retry_after_header=True,
    raise_on_status=False,             # hand back the last response instead of raising
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))

resp = session.get(
    "https://sellermagnet-api.com/api/amazon-product-lookup",
    params={"asin": "B0CLTBHXWQ", "marketplaceId": "APJ6JRA9NG5V4",
            "api_key": os.environ["SELLERMAGNET_API_KEY"]},
    timeout=60,
)
print(resp.status_code)

How do I see the headers when debugging?

Add -i to curl to print the status line and headers above the body. A 429 shows Retry-After: 2 and a message naming the concurrency cap; a 503 carries a Retry-After of 30 or 60 seconds.

Print status and headers for one request

curl -si "https://sellermagnet-api.com/api/amazon-product-lookup" \
  --get \
  --data-urlencode "asin=B0CLTBHXWQ" \
  --data-urlencode "marketplaceId=APJ6JRA9NG5V4" \
  --data-urlencode "api_key=$SELLERMAGNET_API_KEY" | head -n 20

A missing product is not a transient error

A 404 for an ASIN that does not exist in that marketplace has already been charged. Retrying it spends another credit to learn the same thing, so store it as missing and move on.

Before going to production, check the API status page for any open incident, and see pricing for how credits are counted. A request is charged one credit once it clears validation, authentication and the concurrency queue, whatever happens next - including a 404, 502 or 503.

Frequently Asked Questions

Does a 429 response cost a credit?

No. A 429 is returned before the credit is charged, so waiting and retrying costs only the successful attempt.

How long should I wait after a 429?

At least the Retry-After value: 2 seconds for the per-key concurrency cap. The per-IP 429 sends no Retry-After, so fall back to exponential backoff with jitter.

Why did I get a 401 with a valid key?

Read the message. A 401 also means the account has no credits left, or a temporary key-validation error that is safe to retry with backoff.

Should I retry a 404?

No. The product or seller does not exist in that marketplace, and the request has already been charged.

What is the rate limit per IP address?

600 requests per minute and 15,000 per hour per client IP as of September 2026, alongside the per-key concurrency cap.

Bottom line: retry 429, 502 and 503 with backoff and Retry-After, stop on everything else, and never re-request a 404. That policy keeps both your error rate and your credit spend flat. Ready-made snippets for other languages are on the code examples page.

Ready to Extract Amazon Data at Scale?

Start building with SellerMagnet API today. Real-time product data, competitive pricing, and review analytics at your fingertips.

500 free API credits • No credit card required • Cancel anytime