If you manage Amazon Advertising at scale — dozens of campaigns, thousands of keywords, multiple marketplaces — manual bid management in Seller Central or Vendor Central becomes a bottleneck. Hourly bid adjustments, keyword harvesting from Search Term Reports, and daily performance reporting all take time that could be automated.
The Amazon Advertising API provides programmatic access to everything in the Ads console. This guide covers authentication, the key endpoints you’ll actually use, automating bid changes based on performance rules, and building a lightweight reporting pipeline.
The Amazon Advertising API vs SP-API
These are two separate APIs that often get confused:
| Amazon Advertising API | SP-API | |
|---|---|---|
| Purpose | Manage and report on advertising campaigns | Access seller data (orders, inventory, listings) |
| Cost (2025) | Free to use | $1,400/year subscription + usage fees |
| Auth system | Login with Amazon (LWA) OAuth | Login with Amazon (LWA) OAuth |
| Use for | Bids, campaigns, keywords, search terms, DSP | Orders, inventory, listings, pricing |
The Advertising API is free to use — no subscription fees, no usage billing. This makes it significantly more accessible for sellers building custom tooling.
Official reference: Amazon Advertising API documentation
Setting Up API Access
Step 1: Become an Amazon Advertising API Developer
- Go to advertising.amazon.com → API → Request Access
- Fill in the developer registration form — include a description of what you’re building
- Wait for approval (typically 1–3 business days for legitimate seller use cases)
Step 2: Create an LWA App
Once approved:
- Go to developer.amazon.com → Login with Amazon
- Create a New Security Profile
- Note your Client ID and Client Secret
Step 3: Authorise Your Account
The Advertising API uses OAuth 2.0. You need to authorise your Amazon Ads account:
- Direct the account owner to this URL (replace placeholders):
https://www.amazon.com/ap/oa?client_id=YOUR_CLIENT_ID
&scope=advertising::campaign_management
&response_type=code
&redirect_uri=YOUR_REDIRECT_URI
&state=random_state_string
-
After authorisation, you receive a
codeparameter in the redirect URL -
Exchange it for tokens:
import requests
token_response = requests.post(
'https://api.amazon.com/auth/o2/token',
data={
'grant_type': 'authorization_code',
'code': 'YOUR_AUTH_CODE',
'redirect_uri': 'YOUR_REDIRECT_URI',
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET'
}
)
tokens = token_response.json()
access_token = tokens['access_token']
refresh_token = tokens['refresh_token'] # Store this securely — it's long-lived
Step 4: Get Your Profile ID
Most Advertising API endpoints require a Profile ID — the identifier for a specific advertiser account in a specific marketplace.
headers = {
'Authorization': f'Bearer {access_token}',
'Amazon-Advertising-API-ClientId': 'YOUR_CLIENT_ID',
'Content-Type': 'application/json'
}
profiles_response = requests.get(
'https://advertising-api.amazon.com/v2/profiles',
headers=headers
)
profiles = profiles_response.json()
# Each profile = one marketplace account
for profile in profiles:
print(f"Profile ID: {profile['profileId']}, Marketplace: {profile['countryCode']}, Type: {profile['accountInfo']['type']}")
Add the chosen profile ID to all subsequent requests:
headers['Amazon-Advertising-API-Scope'] = 'YOUR_PROFILE_ID'
Refreshing Access Tokens
Access tokens expire after 1 hour. Use the refresh token to get a new one:
def refresh_access_token(refresh_token, client_id, client_secret):
response = requests.post(
'https://api.amazon.com/auth/o2/token',
data={
'grant_type': 'refresh_token',
'refresh_token': refresh_token,
'client_id': client_id,
'client_secret': client_secret
}
)
return response.json()['access_token']
Store the refresh token in a secrets manager (AWS Secrets Manager, HashiCorp Vault, or environment variable) — never in source code.
Key Endpoints for Sponsored Products
The Sponsored Products API (v2 and v3) covers the most common use cases for sellers.
List Campaigns
response = requests.get(
'https://advertising-api.amazon.com/v2/sp/campaigns',
headers=headers,
params={
'stateFilter': 'enabled',
'count': 100
}
)
campaigns = response.json()
List Ad Groups
response = requests.get(
'https://advertising-api.amazon.com/v2/sp/adGroups',
headers=headers,
params={
'campaignId': campaign_id,
'stateFilter': 'enabled'
}
)
Get Keywords with Bids
response = requests.get(
'https://advertising-api.amazon.com/v2/sp/keywords',
headers=headers,
params={
'adGroupId': ad_group_id,
'stateFilter': 'enabled,paused',
'count': 500
}
)
keywords = response.json()
Update Keyword Bids
update_payload = [
{
'keywordId': keyword['keywordId'],
'bid': new_bid_value,
'state': 'enabled'
}
for keyword in keywords_to_update
]
response = requests.put(
'https://advertising-api.amazon.com/v2/sp/keywords',
headers=headers,
json=update_payload
)
Building an Automated Bid Manager
Here’s a practical bid automation script that adjusts keyword bids based on ACoS (Advertising Cost of Sale) targets:
import requests
import json
from datetime import datetime, timedelta
class AmazonAdsBidManager:
def __init__(self, access_token, client_id, profile_id):
self.headers = {
'Authorization': f'Bearer {access_token}',
'Amazon-Advertising-API-ClientId': client_id,
'Amazon-Advertising-API-Scope': profile_id,
'Content-Type': 'application/json'
}
self.base_url = 'https://advertising-api.amazon.com'
def get_keyword_performance(self, ad_group_id, days=14):
"""Fetch keyword performance report for the past N days."""
end_date = datetime.now().strftime('%Y%m%d')
start_date = (datetime.now() - timedelta(days=days)).strftime('%Y%m%d')
# Request report
report_request = requests.post(
f'{self.base_url}/reporting/reports',
headers=self.headers,
json={
"name": f"Keyword Report {end_date}",
"startDate": start_date,
"endDate": end_date,
"configuration": {
"adProduct": "SPONSORED_PRODUCTS",
"groupBy": ["keyword"],
"columns": [
"keywordId", "keywordText", "matchType",
"impressions", "clicks", "spend",
"sales1d", "sales7d", "sales14d", "sales30d",
"orders1d", "orders7d"
],
"reportTypeId": "spKeywords",
"timeUnit": "SUMMARY",
"format": "GZIP_JSON"
}
}
)
return report_request.json().get('reportId')
def calculate_new_bid(self, current_bid, spend, sales, target_acos=0.25):
"""
Calculate adjusted bid based on actual vs target ACoS.
ACoS = Spend / Sales
If ACoS > target: reduce bid (overspending)
If ACoS < target: increase bid (room to spend more)
"""
if sales == 0:
if spend > 5.0: # Spending with no sales — reduce significantly
return max(current_bid * 0.7, 0.02)
return current_bid # No data yet — don't change
actual_acos = spend / sales
if actual_acos == 0:
return current_bid
# Adjustment factor: ratio of target ACoS to actual ACoS
# If actual ACoS is 0.30 and target is 0.25: factor = 0.25/0.30 = 0.833 → reduce bid
# If actual ACoS is 0.20 and target is 0.25: factor = 0.25/0.20 = 1.25 → increase bid
adjustment_factor = target_acos / actual_acos
# Cap adjustments to prevent drastic changes in one pass
adjustment_factor = max(0.75, min(1.25, adjustment_factor))
new_bid = round(current_bid * adjustment_factor, 2)
# Enforce min/max bid guardrails
new_bid = max(0.02, min(new_bid, 10.00))
return new_bid
def run_bid_optimisation(self, campaign_id, target_acos=0.25, min_clicks=10):
"""
Full bid optimisation pass for all keywords in a campaign.
Only adjusts keywords with sufficient click data.
"""
# Get ad groups in campaign
ad_groups_resp = requests.get(
f'{self.base_url}/v2/sp/adGroups',
headers=self.headers,
params={'campaignId': campaign_id, 'stateFilter': 'enabled'}
)
ad_groups = ad_groups_resp.json()
updates = []
for ad_group in ad_groups:
keywords_resp = requests.get(
f'{self.base_url}/v2/sp/keywords',
headers=self.headers,
params={
'adGroupId': ad_group['adGroupId'],
'stateFilter': 'enabled',
'count': 500
}
)
keywords = keywords_resp.json()
# Get performance data (simplified — in production, use the Reports API)
for kw in keywords:
clicks = kw.get('extendedData', {}).get('clicks', 0)
spend = kw.get('extendedData', {}).get('cost', 0) / 1_000_000 # Amazon returns microcurrency
sales = kw.get('extendedData', {}).get('attributedSales14d', 0) / 1_000_000
if clicks < min_clicks:
continue # Not enough data
current_bid = kw['bid']
new_bid = self.calculate_new_bid(current_bid, spend, sales, target_acos)
if new_bid != current_bid:
updates.append({
'keywordId': kw['keywordId'],
'bid': new_bid,
'state': 'enabled'
})
print(f" {kw['keywordText']}: £{current_bid:.2f} → £{new_bid:.2f} "
f"(ACoS: {spend/sales*100:.1f}% → target {target_acos*100:.0f}%)")
# Apply updates in batches of 100
if updates:
for i in range(0, len(updates), 100):
batch = updates[i:i+100]
requests.put(
f'{self.base_url}/v2/sp/keywords',
headers=self.headers,
json=batch
)
print(f"Updated {len(updates)} keyword bids")
return len(updates)
Building a Search Term Harvesting Pipeline
Search Term Reports reveal which actual customer search queries triggered your ads — and which ones are converting. Automating keyword harvesting from these reports is one of the highest-ROI automations for Amazon advertising.
def harvest_converting_search_terms(report_data, min_orders=2, max_acos=0.30):
"""
Extract high-performing search terms to add as exact match keywords.
Args:
report_data: Parsed search term report (list of dicts)
min_orders: Minimum orders to consider a term proven
max_acos: Maximum ACoS threshold for harvesting
"""
candidates = []
for term in report_data:
orders = term.get('orders14d', 0)
spend = term.get('spend', 0)
sales = term.get('sales14d', 0)
if orders < min_orders:
continue
acos = spend / sales if sales > 0 else 999
if acos <= max_acos:
candidates.append({
'search_term': term['searchTerm'],
'orders': orders,
'acos': round(acos * 100, 1),
'spend': round(spend, 2),
'sales': round(sales, 2),
'recommended_bid': round(
(sales / orders) * max_acos * 0.8, 2 # Bid at 80% of target ACoS CPA
)
})
# Sort by orders descending
return sorted(candidates, key=lambda x: x['orders'], reverse=True)
Output from this function gives you a list of search terms, their performance, and a recommended starting bid — ready to add as exact match keywords programmatically.
Scheduling with n8n or Cron
For production use, these scripts should run on a schedule:
Daily at 6 AM: Pull Search Term Report → harvest converting terms → add as new exact match keywords
Daily at 7 AM: Run bid optimisation for all active campaigns using 14-day data
Weekly on Mondays: Pull campaign performance summary → post to Slack
With n8n: use a Schedule Trigger + Execute Command node (if running Python) or the HTTP Request node with direct API calls.
With Linux cron:
# /etc/cron.d/amazon-ads
0 6 * * * /usr/bin/python3 /opt/amazon-ads/harvest_keywords.py >> /var/log/amazon-ads.log 2>&1
0 7 * * * /usr/bin/python3 /opt/amazon-ads/bid_optimiser.py >> /var/log/amazon-ads.log 2>&1
0 9 * * 1 /usr/bin/python3 /opt/amazon-ads/weekly_report.py >> /var/log/amazon-ads.log 2>&1
Automating Amazon Advertising bidding and reporting is one of the clearest ROI cases in e-commerce tooling. The API is free, the data is rich, and the performance improvement from consistent, rule-based bid management typically outperforms manual management as campaign scale increases.
We build Amazon Advertising API integrations — bid automation, keyword harvesting pipelines, performance dashboards, and multi-marketplace reporting. Book a free consultation to discuss your advertising automation requirements.