Jeffrey Hakim Logo Home

Amazon Seller Central

Automating Amazon Seller Central: A Python Workflow for Repricing, Inventory Sync, and Ad Optimization

7 min read

3D futuristic shopping cart concept illustrating high-velocity e-commerce sales funnels and conversion optimization.

Automating Amazon Seller Central: A Python Workflow for Repricing, Inventory Sync, and Ad Optimization

Most "Amazon automation" content is written by people who have never opened the Selling Partner API docs. It's AI-summary-of-an-AI-summary, three years out of date, and useless the moment you try to actually connect to your seller account. This isn't that. This is the workflow I run in production: a Python service that talks directly to Amazon's SP-API and Ads API to keep pricing, inventory, and PPC spend in sync without me babysitting Seller Central all day.

If you're running a Shopify storefront alongside an Amazon channel — which is the reality for most brands I work with — the two platforms need fundamentally different automation strategies. I covered the conversion-side playbook for Shopify here. Amazon is the opposite problem: you don't control the storefront, so the leverage is entirely in the backend — pricing velocity, inventory accuracy, and ad efficiency. That's what this post automates.

Why Automate Amazon Seller Central Right Now

Amazon's FBA New Selection Program launched July 30, 2026, with larger fee credits and lower referral fees on qualifying new ASINs — which means more sellers are about to onboard SKUs faster than their manual pricing and inventory processes can handle. At the same time, Amazon raised the minimum delivery-speed bar for Seller Fulfilled Prime, adding per-ZIP delivery promise requirements inside Seller Central. Both changes push in the same direction: sellers who automate the operational layer move faster and get penalized less than sellers doing it by hand in spreadsheets.

The three highest-leverage things to automate, in order of ROI:

  1. Repricing — margin-aware, buy-box-aware, updated continuously instead of once a day.

  2. Inventory sync — keeping FBA stock levels reconciled against your source of truth (Shopify, WMS, or ERP) so you never oversell or trigger a stranded-inventory flag.

  3. Ad spend optimization — pausing or reallocating PPC budget based on ACOS and conversion data rather than checking campaigns manually.

Step 1: Authenticate with the SP-API

Everything starts with Login with Amazon (LWA). You generate a refresh token once during app authorization in Seller Central, then exchange it for short-lived access tokens (1-hour TTL) on every call. Save that refresh token immediately when you get it — Amazon shows it to you exactly once.


python

import requests
import time

LWA_TOKEN_URL = "https://api.amazon.com/auth/o2/token"

class SPAPIAuth:
    def __init__(self, refresh_token, client_id, client_secret):
        self.refresh_token = refresh_token
        self.client_id = client_id
        self.client_secret = client_secret
        self._access_token = None
        self._expires_at = 0

    def get_access_token(self):
        if self._access_token and time.time() < self._expires_at - 60:
            return self._access_token

        resp = requests.post(LWA_TOKEN_URL, data={
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        })
        resp.raise_for_status()
        data = resp.json()
        self._access_token = data["access_token"]
        self._expires_at = time.time() + data["expires_in"]
        return self._access_token
import requests
import time

LWA_TOKEN_URL = "https://api.amazon.com/auth/o2/token"

class SPAPIAuth:
    def __init__(self, refresh_token, client_id, client_secret):
        self.refresh_token = refresh_token
        self.client_id = client_id
        self.client_secret = client_secret
        self._access_token = None
        self._expires_at = 0

    def get_access_token(self):
        if self._access_token and time.time() < self._expires_at - 60:
            return self._access_token

        resp = requests.post(LWA_TOKEN_URL, data={
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        })
        resp.raise_for_status()
        data = resp.json()
        self._access_token = data["access_token"]
        self._expires_at = time.time() + data["expires_in"]
        return self._access_token
import requests
import time

LWA_TOKEN_URL = "https://api.amazon.com/auth/o2/token"

class SPAPIAuth:
    def __init__(self, refresh_token, client_id, client_secret):
        self.refresh_token = refresh_token
        self.client_id = client_id
        self.client_secret = client_secret
        self._access_token = None
        self._expires_at = 0

    def get_access_token(self):
        if self._access_token and time.time() < self._expires_at - 60:
            return self._access_token

        resp = requests.post(LWA_TOKEN_URL, data={
            "grant_type": "refresh_token",
            "refresh_token": self.refresh_token,
            "client_id": self.client_id,
            "client_secret": self.client_secret,
        })
        resp.raise_for_status()
        data = resp.json()
        self._access_token = data["access_token"]
        self._expires_at = time.time() + data["expires_in"]
        return self._access_token

In production I use the python-amazon-sp-api library instead of hand-rolling this — it wraps auth, request signing, and rate-limit backoff — but it's worth understanding the raw token exchange since that's where most integrations silently break (expired refresh tokens after credential rotation, wrong region endpoint, clock skew on expires_in).

Step 2: Pull Inventory and Reprice Against Buy Box Data

The FBA Inventory API gives you real-time stock levels per SKU; the Product Pricing API gives you competitive offers including buy-box price. Combine them and you can run a repricer that respects your floor margin instead of racing to the bottom.


python

from sp_api.api import Inventories, Products
from sp_api.base import Marketplaces

inventory = Inventories(marketplace=Marketplaces.US)
pricing = Products(marketplace=Marketplaces.US)

def get_reprice_target(sku, asin, floor_price, target_margin_price):
    stock = inventory.get_inventory_summary_marketplace(
        sellerSkus=[sku]
    ).payload["inventorySummaries"][0]

    if stock["totalQuantity"] == 0:
        return None  # don't reprice OOS listings

    offers = pricing.get_item_offers(asin, item_condition="New").payload
    buy_box_price = next(
        (o["ListingPrice"]["Amount"] for o in offers.get("Offers", [])
         if o.get("IsBuyBoxWinner")),
        None
    )

    if buy_box_price is None:
        return target_margin_price

    new_price = max(floor_price, round(buy_box_price - 0.01, 2))
    return new_price
from sp_api.api import Inventories, Products
from sp_api.base import Marketplaces

inventory = Inventories(marketplace=Marketplaces.US)
pricing = Products(marketplace=Marketplaces.US)

def get_reprice_target(sku, asin, floor_price, target_margin_price):
    stock = inventory.get_inventory_summary_marketplace(
        sellerSkus=[sku]
    ).payload["inventorySummaries"][0]

    if stock["totalQuantity"] == 0:
        return None  # don't reprice OOS listings

    offers = pricing.get_item_offers(asin, item_condition="New").payload
    buy_box_price = next(
        (o["ListingPrice"]["Amount"] for o in offers.get("Offers", [])
         if o.get("IsBuyBoxWinner")),
        None
    )

    if buy_box_price is None:
        return target_margin_price

    new_price = max(floor_price, round(buy_box_price - 0.01, 2))
    return new_price
from sp_api.api import Inventories, Products
from sp_api.base import Marketplaces

inventory = Inventories(marketplace=Marketplaces.US)
pricing = Products(marketplace=Marketplaces.US)

def get_reprice_target(sku, asin, floor_price, target_margin_price):
    stock = inventory.get_inventory_summary_marketplace(
        sellerSkus=[sku]
    ).payload["inventorySummaries"][0]

    if stock["totalQuantity"] == 0:
        return None  # don't reprice OOS listings

    offers = pricing.get_item_offers(asin, item_condition="New").payload
    buy_box_price = next(
        (o["ListingPrice"]["Amount"] for o in offers.get("Offers", [])
         if o.get("IsBuyBoxWinner")),
        None
    )

    if buy_box_price is None:
        return target_margin_price

    new_price = max(floor_price, round(buy_box_price - 0.01, 2))
    return new_price

Feed the resulting price back through the Listings Items API (or a POST_PRODUCT_PRICING_DATA feed if you're batching hundreds of SKUs). Run this on a 15–30 minute cron cycle — frequent enough to stay competitive, infrequent enough to stay well under SP-API rate limits.

Step 3: Reconcile Inventory Against Your Source of Truth

If you're multichannel, FBA stock and your Shopify/ERP stock will drift the moment you sell through both. A daily reconciliation job that diffs the two and files a JSON_LISTINGS_FEED update to correct FBA-reported quantity prevents the two failure modes that actually cost money: overselling (order defect rate hit) and false stockouts (lost buy box, suppressed listings).


python

def reconcile_inventory(shopify_stock: dict, sp_stock: dict, threshold=2):
    """shopify_stock / sp_stock: {sku: quantity}"""
    discrepancies = []
    for sku, shopify_qty in shopify_stock.items():
        sp_qty = sp_stock.get(sku, 0)
        if abs(shopify_qty - sp_qty) > threshold:
            discrepancies.append({
                "sku": sku,
                "shopify_qty": shopify_qty,
                "sp_qty": sp_qty,
                "action": "sync_to_shopify_truth",
            })
    return discrepancies
def reconcile_inventory(shopify_stock: dict, sp_stock: dict, threshold=2):
    """shopify_stock / sp_stock: {sku: quantity}"""
    discrepancies = []
    for sku, shopify_qty in shopify_stock.items():
        sp_qty = sp_stock.get(sku, 0)
        if abs(shopify_qty - sp_qty) > threshold:
            discrepancies.append({
                "sku": sku,
                "shopify_qty": shopify_qty,
                "sp_qty": sp_qty,
                "action": "sync_to_shopify_truth",
            })
    return discrepancies
def reconcile_inventory(shopify_stock: dict, sp_stock: dict, threshold=2):
    """shopify_stock / sp_stock: {sku: quantity}"""
    discrepancies = []
    for sku, shopify_qty in shopify_stock.items():
        sp_qty = sp_stock.get(sku, 0)
        if abs(shopify_qty - sp_qty) > threshold:
            discrepancies.append({
                "sku": sku,
                "shopify_qty": shopify_qty,
                "sp_qty": sp_qty,
                "action": "sync_to_shopify_truth",
            })
    return discrepancies

Log every correction. When Seller Central flags a SKU for "unexpected inventory changes," you want an audit trail showing the automation, not a guess.

Step 4: Automate PPC Spend with the Amazon Ads API

The Ads API (separate auth flow from SP-API, also LWA-based) exposes campaign, ad group, and keyword-level performance. The highest-value automation here isn't fully autonomous bidding — Amazon's own Performance Max-style automation already does that reasonably well — it's guardrails: automatically pausing keywords that blow past your target ACOS and reallocating budget to winners before the day's spend caps out.


python

def flag_underperformers(keyword_report, target_acos=0.30, min_clicks=15):
    return [
        row for row in keyword_report
        if row["clicks"] >= min_clicks and row["acos"] > target_acos
    ]
def flag_underperformers(keyword_report, target_acos=0.30, min_clicks=15):
    return [
        row for row in keyword_report
        if row["clicks"] >= min_clicks and row["acos"] > target_acos
    ]
def flag_underperformers(keyword_report, target_acos=0.30, min_clicks=15):
    return [
        row for row in keyword_report
        if row["clicks"] >= min_clicks and row["acos"] > target_acos
    ]

Run this hourly during peak dayparts. Pausing a bleeding keyword at 11am instead of catching it in tomorrow's manual review is the difference between a bad hour and a bad week.

Where This Connects to Your Broader Stack

None of this should live in isolation. The same event-driven, data-pipeline thinking I wrote about in building revenue-driven AI systems applies directly here — feed your repricing decisions, inventory reconciliation logs, and ACOS flags into the same data warehouse you're using for AI-assisted reporting, and you get a single operational view across Shopify and Amazon instead of two disconnected dashboards.

A Few Guardrails Before You Ship This

  • Respect rate limits. SP-API throttles per operation, not globally — check the x-amzn-RateLimit-Limit header and back off exponentially rather than hardcoding sleep intervals.

  • Never let repricing go below true floor. Calculate floor from landed cost + FBA fees + referral fee, not just COGS — a lot of "aggressive repricer" horror stories are just bad floor math.

  • Sandbox first. SP-API has a sandbox environment with mock responses. Use it before you point any pricing or feed logic at a live catalog.

  • Watch the SFP delivery-speed change. If you run Seller Fulfilled Prime, the new per-ZIP delivery promise tool changes what "in stock and shippable" means for your automation's assumptions — audit your fulfillment SLAs against it before your reconciliation job starts making decisions on stale rules.

This is the unglamorous half of ecommerce growth — nobody screenshots a reconciliation script for LinkedIn — but it's the half that actually protects margin while the marketing side does its job.

Contact Jeffrey Hakim

Start Your Project Today

Let’s Talk

Prefer email?

jeff@jeffreyhakim.com

Copy Icon
Copied Icon

Copied

What happens next?

I aim to reply within 24 hours

A direct line to Jeffrey — no middlemen, no barriers.

I skip the fluff and get straight to your creative goals.

Jeffrey Hakim creative director and ecommerce strategist
Jeffrey Hakim

Designer, Developer, and Strategist

Contact Jeffrey Hakim

Start Your Project Today

Let’s Talk

Prefer email?

jeff@jeffreyhakim.com

Copy Icon
Copied Icon

Copied

What happens next?

I aim to reply within 24 hours

A direct line to Jeffrey — no middlemen, no barriers.

I skip the fluff and get straight to your creative goals.

Jeffrey Hakim creative director and ecommerce strategist
Jeffrey Hakim

Designer, Developer, and Strategist

Contact Jeffrey Hakim

Start Your Project Today

Let’s Talk

Prefer email?

jeff@jeffreyhakim.com

Copy Icon
Copied Icon

Copied

What happens next?

I aim to reply within 24 hours

A direct line to Jeffrey — no middlemen, no barriers.

I skip the fluff and get straight to your creative goals.

Jeffrey Hakim creative director and ecommerce strategist
Jeffrey Hakim

Designer, Developer, and Strategist