When we launched Reboot Hub — a Hong Kong-based specialist in pre-owned DJI drones and chip-level repair — we had a classic e-commerce SEO problem: 285 products, 50+ collections, and a blog with dozens of articles, all with inconsistent titles, missing schema markup, and zero internal linking strategy.
Doing this manually would take weeks and break the moment we updated anything. So we built a Python automation layer on top of the Shopify Admin API, driven by Claude Code. Here's how it works.
The Problem at Scale
Our Shopify store had accumulated SEO debt fast:
- 285 product listings with inconsistent title formats ("DJI Mini 4 Pro Used" vs "Pre-Owned Mini 4 Pro" vs "DJI Mini 4 Pro Secondhand")
- No JSON-LD schema on any page — Google and AI crawlers had no structured signal about what we sold
- Zero internal links between blog articles and product collections
- No hreflang despite having Russian and Portuguese market pages live
- Missing meta descriptions on most products
Fixing this one-by-one in Shopify admin would have taken weeks and been impossible to maintain.
Architecture: Python + Shopify Admin API
All our SEO scripts talk to the Shopify Admin API using a simple shared pattern:
import requests, os
from dotenv import load_dotenv
load_dotenv()
store = os.getenv('SHOPIFY_STORE_URL')
token = os.getenv('SHOPIFY_ACCESS_TOKEN')
headers = {'X-Shopify-Access-Token': token, 'Content-Type': 'application/json'}
Everything is cursor-paginated to handle our full catalogue without hitting rate limits:
def fetch_all_products():
products = []
url = f'https://{store}/admin/api/2024-01/products.json?limit=250'
while url:
r = requests.get(url, headers=headers)
products.extend(r.json()['products'])
link = r.headers.get('Link', '')
url = next((p.split(';')[0].strip('<>')
for p in link.split(',') if 'next' in p), None)
return products
Step 1: Batch Product Title Standardisation
We defined a brand terminology spec and applied it across all 285 products programmatically:
import re
TITLE_RULES = [
(r'(?i)used', 'Pre-Owned'),
(r'(?i)secondhand', 'Pre-Owned'),
(r'(?i)refurb(ished)?', 'Pristine Pre-Owned'),
]
def normalise_title(title):
for pattern, replacement in TITLE_RULES:
title = re.sub(pattern, replacement, title)
if 'Pre-Owned' in title and not title.startswith('Grade A+'):
title = 'Grade A+ ' + title
return title
Running this across all products took about 4 minutes with rate-limit-aware pacing (Shopify allows 2 req/sec on standard plans).
Step 2: JSON-LD Schema Injection into theme.liquid
Rather than using a Shopify app, we inject JSON-LD directly into theme.liquid via the Assets API. This gives full control over the markup:
ORGANIZATION_SCHEMA = (
"{%- if template == 'index' -%}"
'<script type="application/ld+json">'
'{ "@type": "Organization", "name": "Reboot Hub" }'
"</script>"
"{%- endif -%}"
)
r = requests.get(
f'https://{store}/admin/api/2024-01/themes/{THEME_ID}/assets.json',
params={'asset[key]': 'layout/theme.liquid'},
headers=headers
)
content = r.json()['asset']['value']
updated = content.replace('</head>', ORGANIZATION_SCHEMA + '</head>', 1)
requests.put(
f'https://{store}/admin/api/2024-01/themes/{THEME_ID}/assets.json',
json={'asset': {'key': 'layout/theme.liquid', 'value': updated}},
headers=headers
)
Step 3: Automated Internal Linking
This is the most complex piece. We built a keyword → URL map and a script that:
- Fetches all published blog articles
- Parses the HTML body
- Finds first occurrences of target keywords outside
<h2>,<h3>,<h4>, and existing<a>tags - Wraps them with contextual links
- PUTs the updated body back to Shopify
from bs4 import BeautifulSoup
KEYWORD_LINKS = [
(r'DJI Mini 4 Pro', '/collections/refurbished-dji-mini-series', None),
(r'DJI Air 3S', '/collections/refurbished-dji-air-series', None),
(r'chip.level repair', '/pages/professional-drone-repair-with-genuine-parts', 'chip-level repair'),
# 60+ more entries
]
def inject_links(html, article_url):
soup = BeautifulSoup(html, 'html.parser')
linked = set()
for pattern, target, label in KEYWORD_LINKS:
if target in article_url:
continue # skip self-links
for text_node in soup.find_all(string=re.compile(pattern)):
parent = text_node.parent
if parent.name in ('h2', 'h3', 'h4', 'a') or parent.find_parent('a'):
continue
if pattern in linked:
continue # first occurrence only
new_tag = soup.new_tag('a', href=f'https://reboot-hub.com{target}')
new_tag.string = label or str(text_node)
text_node.replace_with(new_tag)
linked.add(pattern)
return str(soup)
Running this across 40+ articles injected ~280 contextual internal links in about 8 minutes.
Step 4: Programmatic 301 Redirects
When we renamed blog URLs, we generated all redirects from a list instead of clicking through Shopify admin:
REDIRECTS = [
('/blogs/news/buyer-guide-2026', '/blogs/the-reboot-hub-chronicle/buyer-guide-2026'),
('/repair-guide', '/pages/professional-drone-repair-with-genuine-parts'),
# 9 total
]
for path, target in REDIRECTS:
r = requests.post(
f'https://{store}/admin/api/2024-01/redirects.json',
json={'redirect': {'path': path, 'target': target}},
headers=headers
)
print(f'{r.status_code} {path} -> {target}')
Step 5: hreflang for Multilingual Markets
We serve Russian and Portuguese-speaking markets. We injected hreflang tags directly into <head> via the same Assets API approach:
<link rel="alternate" hreflang="en" href="https://reboot-hub.com{{ canonical_url }}" />
<link rel="alternate" hreflang="ru" href="https://reboot-hub.com/ru{{ canonical_url }}" />
<link rel="alternate" hreflang="pt-PT" href="https://reboot-hub.com/pt{{ canonical_url }}" />
<link rel="alternate" hreflang="x-default" href="https://reboot-hub.com{{ canonical_url }}" />
Step 6: llms.txt for AI Crawler Discovery
We also created a llms.txt file — the emerging standard for telling AI systems (ChatGPT, Perplexity, Claude) what your site is about. On Shopify, there's no native way to serve files at the root, so we:
- Created a
page.llms.liquidtemplate with{% layout none %}to strip HTML wrappers - Created a page at
/pages/llms-txtwith the plain-text content - Created a Shopify redirect:
/llms.txt→/pages/llms-txt
The file is now live at reboot-hub.com/llms.txt.
Results After 2 Weeks
| What | Result |
|---|---|
| Product titles | 285 standardised to brand spec |
| Schema | Organization + WebSite JSON-LD on homepage, FAQPage on all articles |
| Internal links | ~280 contextual links injected across blog |
| Redirects | 9 broken chains fixed |
| hreflang | Live for en/ru/pt-PT |
| llms.txt | Published, AI crawlers can discover site structure |
What's Next
We're running a daily pipeline that harvests trending topics from Reddit and Google Trends, generates drafts using a Pinecone RAG index of product manuals, and publishes on a schedule. That's a separate post.
The scripts are internal, but if you're running a Shopify store and want to ask about any specific part — happy to share more in the comments.
Reboot Hub: Grade A+ Pre-Owned DJI drones and chip-level repair, Hong Kong.


























