To rotate an Amazon API key without downtime, generate a second key while the old one keeps working (max 3 active keys per account; up to 3 can run in parallel), confirm the new key is live with a zero-credit check, move real traffic onto it with the old key kept as a fallback, then delete the old key. Resetting a key is a different action: it swaps the value of the same key slot immediately, so it breaks whatever still uses the old value and is not a zero-downtime rotation on its own.
Key Takeaways
- A request with no parameters at all checks a key for free: a dead key answers 401 before any parameter is read, a live key answers the parameter gate's 400 - both cost 0 credits.
- Credits live on the account, not on any one key, so a new key and an old key can run side by side for free during a rotation.
- Reset replaces the value of the same key slot at once; the old value stops working immediately, so Reset is for killing a leaked key, not for a planned rotation.
- The max-3 limit counts every key that is not deleted, whether it is active or toggled off - only Delete frees a slot for a new key.
- A client that falls back to the old key on a 401 from the new one keeps every request working through the whole overlap window.
How do I check a new API key for free?
Every endpoint checks the API key before it reads any parameter, and the parameter gate runs before a credit is ever deducted. So a request with no parameters at all costs 0 credits either way: a dead or inactive key answers 401 "Invalid or inactive API key.", a key on an account with 0 credits ALSO answers 401 but with a different message, "Insufficient user credits.", and a live key with credits answers the parameter gate's 400 "Missing required parameter(s): asin, marketplaceId". Read the message, not just the status code, or a freshly generated key on an empty account looks identical to a dead one. Checking this as many times as you like during a rotation never spends a credit.
The free check's response body on a live key
{
"success": false,
"error": "Bad request",
"message": "Missing required parameter(s): asin, marketplaceId"
}
Reset, Toggle off or Delete: which one for a rotation?
Reset keeps the same key slot but swaps its value right away, so it is the opposite of zero-downtime: use it to kill a leaked key immediately. Toggle off pauses a key but does not delete it, and it still counts toward the 3-key limit, so it does not free up room for a new key either. Only Delete (a soft delete; the row and its usage logs stay for billing evidence) removes a key from the count, which is what makes room for the next rotation.
| Action | What changes | Counts toward 3 | Old value |
|---|---|---|---|
| Generate | new key_id + value | YES, counts toward 3 | unaffected (shared credit pool) |
| Reset | same key_id, new value | no change | old value dead at once |
| Toggle off | is_active = false | YES, still counts | unaffected (just paused) |
| Delete | soft delete, deleted_at set | NO, frees the slot | unaffected |

What does a zero-downtime rotation look like in code?
The script below generates (manually, in the dashboard) a second key, then checks it for free before sending any real traffic to it. Every real lookup tries the new key first; if the new key answers 401, the call falls back to the old key instead of failing, so nothing a user sees breaks during the overlap. It uses sellermagnet.py (API, ApiError) and retry.py (get_with_retry) from the Python quickstart, but builds its own session per key instead of calling new_session(), because it needs two keys open at once.
rotate_key.py - check the new key for free, then switch with a fallback
"""Zero-downtime Amazon API key rotation: confirm the new key for free, then move real traffic
onto it while the old key still answers as a fallback, and know when it is safe to delete the
old key.
Credits live on the ACCOUNT, not the key (sellermagnet.com API key transport facts): running the
old and the new key side by side during a rotation costs nothing extra, both draw the same
balance. The check below sends no parameters at all, which is free either way: the key is checked
BEFORE any parameter, so a dead key answers 401 "Invalid or inactive API key." and a live key
answers the parameter gate's 400 "Missing required parameter(s): asin, marketplaceId" - neither
response deducts a credit.
Helpers: sellermagnet.py (API, ApiError, get) and retry.py (get_with_retry) from
https://sellermagnet-api.com/blog/amazon-product-api-python; this script builds its own sessions
instead of calling sellermagnet.new_session(), because it needs two keys open at the same time.
"""
import os
import re
import requests
from retry import get_with_retry
from sellermagnet import API, ApiError
MARKETPLACE = "ATVPDKIKX0DER" # amazon.com
ASINS = ["B0CL61F39H", "B0NOOFFR01", "B0FLAKY001", "not-an-asin"]
MAX_FAILED = 3 # 502/503 are billed every attempt
def session_for(key: str) -> requests.Session:
session = requests.Session()
session.headers["X-Api-Key"] = key
return session
def key_is_live(key: str) -> str:
"""Zero-credit check: no parameters at all, so every answer is free. The key is checked before
any parameter is read, so a dead key answers 401 "Invalid or inactive API key." and a zero-credit
account also answers 401, but with "Insufficient user credits." instead - distinct messages, same
status. A valid key with credits answers the parameter gate's 400 "Missing required parameter(s)".
Returns "live", "dead" or "no-credits"."""
resp = session_for(key).get(f"{API}/amazon-product-lookup", timeout=120)
try:
message = resp.json().get("message", "")
except ValueError:
message = ""
if resp.status_code == 401 and "Insufficient user credits" in message:
return "no-credits"
if resp.status_code == 401:
return "dead"
if resp.status_code == 400 and "Missing required parameter" in message:
return "live"
raise RuntimeError(f"unexpected key-check response {resp.status_code}: {resp.text[:200]}")
def rotate(old_key: str, new_key: str) -> None:
status = key_is_live(new_key)
if status == "dead":
print("ABORT: new key is not live yet (401) - keep sending traffic on the old key.")
return
if status == "no-credits":
print("ABORT: new key is valid but the account has 0 credits - add credits before switching.")
return
print("new key confirmed live (free check, 0 credits spent)\n")
primary, fallback = session_for(new_key), session_for(old_key)
used_fallback = failed = 0
for raw in ASINS:
asin = raw.strip().upper()
if not re.fullmatch(r"[A-Z0-9]{10}", asin):
print(f"{raw:<11} skipped: not a 10-character ASIN")
continue
if failed >= MAX_FAILED:
print(f"stopped after {MAX_FAILED} failed calls")
return
try:
data = get_with_retry(primary, "amazon-product-lookup", asin=asin, marketplaceId=MARKETPLACE)
holder = "new"
except ApiError as new_err:
if new_err.status != 401:
label = "not found (billed 404)" if new_err.status == 404 else f"error {new_err}"
print(f"{asin:<11} new {label}")
failed += new_err.status >= 500
continue
used_fallback += 1
try:
data = get_with_retry(fallback, "amazon-product-lookup", asin=asin, marketplaceId=MARKETPLACE)
holder = "old (fallback)"
except ApiError as old_err:
print(f"{asin:<11} both keys failed: {old_err}")
failed += old_err.status >= 500
continue
price = data["buyBoxInfo"].get("price")
shown = f"{price:.2f}" if isinstance(price, (int, float)) else "N/A"
print(f"{asin:<11} {holder:<14} {shown}")
print(f"\nfallback calls used: {used_fallback}")
print("safe to delete the old key now" if used_fallback == 0 else
"keep the old key active - traffic is still using it")
if __name__ == "__main__":
rotate(os.environ["SELLERMAGNET_OLD_KEY"], os.environ["SELLERMAGNET_NEW_KEY"])
Output (illustrative data: a clean rotation, one refunded 500, no fallback needed)
new key confirmed live (free check, 0 credits spent)
B0CL61F39H new 444.99
B0NOOFFR01 new N/A
B0FLAKY001 new 19.99
not-an-asin skipped: not a 10-character ASIN
fallback calls used: 0
safe to delete the old key now
- new key confirmed live means the zero-credit check passed; nothing has been charged yet and no real traffic has moved.
- fallback calls used: 0 means every real lookup succeeded on the new key - safe to delete the old key right after.
- A 401 from the new key mid-rotation is not an error to panic over: the script falls back to the old key for that one call and keeps counting, so you see exactly how many requests still needed the old key before you delete it.
- Never delete the old key while fallback calls are still happening - the counter at the end of the run is the signal, not a fixed wait time.

What does a key rotation cost?
Nothing beyond the normal 1 credit per real request: the zero-parameter check is free on both the new and the old key, and running two keys at once costs nothing extra because credits are tracked per account, not per key. The only thing a rotation spends is the same credits your traffic would have spent anyway, whichever key answers it. For the difference between sending a key as a header or a query parameter, see API key: header vs. query string.
Frequently Asked Questions
Does rotating an API key cost credits?
No. Generating, resetting, toggling and deleting a key are account actions with no credit cost, and credits live on the account, not on any one key, so running an old and a new key side by side during a rotation spends nothing extra.
Is Reset a zero-downtime rotation?
No. Reset keeps the same key slot but swaps its value immediately, so whatever still uses the old value breaks at once. Use Reset only when a key leaked and needs to die right now; for a planned rotation, generate a second key instead.
Why does deleting an old key matter if I already turned it off?
The 3-key limit counts every key that is not deleted, active or not. A toggled-off key still fills one of your 3 slots, so the next rotation has nowhere to put a new key until the old one is actually deleted.
How do I check a new key works before switching real traffic to it?
Send a request to any endpoint with no parameters at all. A dead key answers 401 before the parameters are even read; a live key answers the parameter gate's free 400. Both responses cost 0 credits, so you can check as often as you like.
What happens if I switch traffic to the new key too early?
Nothing dangerous: a request on a key that is not yet valid gets a free 401, so a client that falls back to the old key on 401 keeps working through the whole overlap window with no failed request reaching your users.
Why does my new key's check also come back 401?
A dead key and a zero-credit account both answer 401, with different message text: "Invalid or inactive API key." versus "Insufficient user credits." A freshly generated key on an account with no credits left looks dead unless you read the message, not just the status code.
Bottom line: generate a second key, prove it is live with a free check, move traffic with an old-key fallback, and delete (not just toggle off) the old key once the fallback count hits zero. The API keys page in the dashboard is where Generate, Reset, Toggle and Delete all live, and a free account includes 150 credits to try it.