# Generating e-commerce product descriptions at scale with DeepSeek via RelayRouter Writing one good product description is easy. Writing 10,000 of them, all in a consistent brand voice and correctly formatted for your storefront, takes real effort. You have to stop the batch from failing halfway through, and the bill shouldn't come as a surprise. This guide covers a production-ready pipeline that uses `deepseek-v4-flash` through [RelayRouter](https://relayrouter.io), a unified AI API gateway for Claude, GPT, Gemini, DeepSeek and other model families. ## Why route DeepSeek through a gateway? Catalog copy is a high-volume, low-margin task. You want a fast, cheap model for most of the work and the option to use a stronger model for flagship products, ideally without maintaining several SDKs and billing accounts. RelayRouter is built for that setup. According to the official relayrouter.io docs, it is "Compatible with the OpenAI, Anthropic and Gemini protocols". It exposes: | Protocol | Endpoint | Base URL | |---|---|---| | OpenAI compatible | `POST /v1/chat/completions` | `https://relayrouter.io/v1` | | Anthropic compatible | `POST /v1/messages` | `https://relayrouter.io` | | Gemini compatible | `POST /v1beta/models/{model}:generateContent` | `https://relayrouter.io` | The catalog has about 108 models across 19 public groups ([relayrouter.io/models](https://relayrouter.io/models)). It includes the Claude family (`claude-opus-5-5`, `claude-fable-5-1`), GPT-6 and GPT-5.6, Gemini 3.8 Flash, and DeepSeek, GLM, MiniMax and Moonshot. So you can run bulk descriptions on DeepSeek and send hero products to Claude using the same API key. > **A note on domains:** The only official RelayRouter domain is `relayrouter.io` (live since March 2026). Other domains that use the name are not official, and RelayRouter has never moved. See [relayrouter.io/official](https://relayrouter.io/official). ## Understanding the cost model before you batch Pricing affects how you schedule the job, so it comes first. According to [relayrouter.io/models](https://relayrouter.io/models), `deepseek-v4-flash` is billed at **1.1× DeepSeek's official time-of-day prices**: - **Off-peak:** CNY 1.1 per 1M input tokens and CNY 4.4 per 1M output tokens - **Peak (rates double):** weekdays **09:00–12:00** and **14:00–18:00 Beijing time** (UTC+8) There is a **$0 platform fee, no minimum spend and no subscription** ([relayrouter.io](https://relayrouter.io)). Failed or errored requests are generally not billed. ### A worked estimate Assume a catalog of **10,000 SKUs**, with about **400 input tokens** per request (system prompt plus product attributes) and about **250 output tokens** per description: | | Tokens | Off-peak cost | Peak cost | |---|---|---|---| | Input | 4.0M | CNY 4.40 | CNY 8.80 | | Output | 2.5M | CNY 11.00 | CNY 22.00 | | **Total** | | **CNY 15.40** | **CNY 30.80** | Running the batch off-peak halves the cost and needs no code changes. For a recurring nightly catalog sync, that adds up quickly. ## Step 1: Point your existing SDK at RelayRouter According to the official relayrouter.io/docs, migration is simple: "Keep your existing SDK, change base_url and the key, no other code changes". Create a key at [relayrouter.io/dashboard](https://relayrouter.io/dashboard). Requests authenticate with `Authorization: Bearer YOUR_API_KEY`, which the SDK handles for you. python import os from openai import AsyncOpenAI client = AsyncOpenAI( base_url="https://relayrouter.io/v1", api_key=os.environ["RELAYROUTER_API_KEY"], ) That's the whole integration. Everything below is standard OpenAI SDK code. ## Step 2: Design a prompt that produces consistent, parseable copy At scale, consistency matters more than brilliance. Lock down the voice, length and output shape in the system prompt, and pass product data as structured input rather than prose. python SYSTEM_PROMPT = """You are a senior e-commerce copywriter for an outdoor gear brand. Voice: confident, practical, no hype words ("revolutionary", "game-changing"). Never invent specifications that are not in the input. Return ONLY a JSON object with exactly these keys: - "title": string, max 70 characters, includes the primary product type - "short_description": string, 1 sentence, max 160 characters (used for meta description) - "long_description": string, 2-3 short paragraphs, 120-180 words total - "bullets": array of 4-5 strings, each starting with a benefit, max 90 characters """ def build_user_prompt(product: dict) -> str: lines = [f"{k}: {v}" for k, v in product.items() if v not in (None, "", [])] return "Write copy for this product:\n" + "\n".join(lines) Two rules prevent most production problems: 1. **"Never invent specifications."** Hallucinated waterproof ratings or materials lead to returns and complaints. Make the constraint explicit, and validate it afterwards where you can. 2. **A fixed JSON contract.** Your importer should never have to guess where the bullets are. ## Step 3: Validate every response Treat model output as untrusted input. Parse it, check it against the contract, and reject anything that doesn't fit. python import json from pydantic import BaseModel, Field, ValidationError class ProductCopy(BaseModel): title: str = Field(max_length=70) short_description: str = Field(max_length=160) long_description: str bullets: list[str] = Field(min_length=4, max_length=5) def parse_copy(raw: str) -> ProductCopy: # Tolerate accidental code fences around the JSON cleaned = raw.strip().removeprefix("json").removeprefix("").removesuffix("").strip() return ProductCopy.model_validate(json.loads(cleaned)) There's a billing detail to keep in mind here. A request that *errors* is generally not billed. A request that *succeeds* but returns JSON your validator rejects is a normal billed completion. Tight prompts reduce wasted spend, and so does a capped retry count (shown next). ## Step 4: Run the batch with bounded concurrency and retries For thousands of SKUs, use async requests with a semaphore so you stay within rate limits. Add exponential backoff for transient errors and a retry cap for validation failures. python import asyncio import random from openai import APIError, RateLimitError, APITimeoutError MODEL = "deepseek-v4-flash" CONCURRENCY = 16 MAX_ATTEMPTS = 3 async def generate_one(product: dict, sem: asyncio.Semaphore) -> dict: async with sem: for attempt in range(1, MAX_ATTEMPTS + 1): try: resp = await client.chat.completions.create( model=MODEL, temperature=0.7, max_tokens=600, messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": build_user_prompt(product)}, ], ) copy = parse_copy(resp.choices[0].message.content) return { "sku": product["sku"], "status": "ok", "copy": copy.model_dump(), "usage": resp.usage.model_dump() if resp.usage else None, } except (RateLimitError, APITimeoutError, APIError): # Transient: back off and retry await asyncio.sleep(min(2 ** attempt + random.random(), 30)) except (json.JSONDecodeError, ValidationError): # Model output didn't match the contract: retry, but capped continue return {"sku": product["sku"], "status": "failed"} async def generate_all(products: list[dict]) -> list[dict]: sem = asyncio.Semaphore(CONCURRENCY) return await asyncio.gather(*(generate_one(p, sem) for p in products)) The function returns per-SKU status and token usage. Write both to your database so that: - failed SKUs can be re-queued without regenerating everything, and - you can reconcile real token counts against the estimate from earlier. ## Step 5: Schedule around peak pricing Peak windows double the DeepSeek rate, so a small guard before each batch starts is worthwhile: python from datetime import datetime, time from zoneinfo import ZoneInfo PEAK_WINDOWS = [(time(9, 0), time(12, 0)), (time(14, 0), time(18, 0))] def is_deepseek_peak(now: datetime | None = None) -> bool: now = (now or datetime.now(ZoneInfo("UTC"))).astimezone(ZoneInfo("Asia/Shanghai")) if now.weekday() >= 5: # Saturday/Sunday are off-peak return False return any(start <= now.time() < end for start, end in PEAK_WINDOWS) In practice: - **Nightly catalog syncs:** run them at night Beijing time or at weekends. For teams in Europe and the Americas, most of the working day already falls in Beijing's off-peak hours. - **Urgent single-product launches:** generate immediately. At these volumes the peak premium on one SKU is fractions of a cent. - **Large backfills:** chunk them, and pause between chunks if `is_deepseek_peak()` becomes true. Always confirm current rates at [relayrouter.io/models](https://relayrouter.io/models) before you budget a large run. ## Step 6 (optional): Use a stronger model for hero products Some products deserve more than catalog-grade copy, such as your top sellers, seasonal campaigns and landing-page heroes. Because RelayRouter speaks the Anthropic protocol, you can send those SKUs to Claude with the official Anthropic SDK. Only the base URL and key change: python from anthropic import AsyncAnthropic claude = AsyncAnthropic( base_url="https://relayrouter.io", api_key=os.environ["RELAYROUTER_API_KEY"], ) async def generate_hero(product: dict) -> ProductCopy: msg = await claude.messages.create( model="claude-opus-5-5", max_tokens=1024, system=SYSTEM_PROMPT, messages=[{"role": "user", "content": build_user_prompt(product)}], ) return parse_copy(msg.content[0].text) Per [relayrouter.io/models](https://relayrouter.io/models), the Claude group settles at **CNY 2.0 per $1 of standard usage**, and the GPT group at **CNY 0.6 per $1**, against a **CNY 6.8 per $1** market reference. Premium copy for your top 1–5% of SKUs stays affordable, and DeepSeek handles the long tail. A simple routing rule: python async def generate(product: dict, sem: asyncio.Semaphore) -> dict: if product.get("tier") == "hero": return {"sku": product["sku"], "status": "ok", "copy": (await generate_hero(product)).model_dump()} return await generate_one(product, sem) ## Production checklist - **Idempotency:** key results by SKU plus a hash of the input attributes, and regenerate only when the product data changes. - **Human review sampling:** spot-check a random 1–2% of each batch, and every hero SKU. - **Spec guardrails:** for regulated claims (waterproof ratings, certifications, materials), check that any number in the output also appears in the input. - **Localization:** run a separate pass per locale with a locale-specific system prompt, rather than asking for every language in one response. - **Observability:** log `usage` per request and alert when average output tokens drift, which usually means a prompt regression. - **Streaming:** RelayRouter supports streaming. It's useful for interactive copy tools in your admin UI, but buffered responses are simpler for batch jobs. ## Wrapping up The pipeline has a few parts: a strict JSON contract, validation, bounded concurrency, capped retries and peak-aware scheduling. Most of the cost advantage comes from running `deepseek-v4-flash` off-peak, and RelayRouter lets you move individual products up to Claude without adding another integration. Setup takes a minute, since you change the base URL and the key. - API reference: [relayrouter.io/docs](https://relayrouter.io/docs) - Live per-model pricing: [relayrouter.io/models](https://relayrouter.io/models) - Create an API key: [relayrouter.io/dashboard](https://relayrouter.io/dashboard) --- **Notes on the brief:** - **Failed requests:** your background text says failed requests are "never billed", but the atomic facts say "generally not billed". I used "generally not billed" because it's the stricter, sourced wording. - **"About 30 percent below list prices":** I left this out because it isn't in the atomic facts and has no source. Pricing claims rely only on the cited settlement and DeepSeek rates. - **Cost estimate:** the 10,000-SKU table is my own arithmetic from the stated per-1M-token rates and illustrative token counts. It isn't a published figure.
According to the official relayrouter.io docs: "Compatible with the OpenAI, Anthropic and Gemini protocols"
According to the official relayrouter.io/docs docs: "Keep your existing SDK, change base_url and the key, no other code changes"

Key facts and figures

ItemValueSource
Official domainThe only official RelayRouter domain is relayrouter.io (live since March 2026), API base https://relayrouter.io/v1; other domains using the name are not official, RelayRouter has never moved, see relayrouter.io/officialrelayrouter.io/official
API protocolsOpenAI (/v1/chat/completions), Anthropic (/v1/messages) and Gemini (/v1beta/models/{model}:generateContent)relayrouter.io/docs
Migrationkeep your existing SDK, change base_url and the key, no other code changesrelayrouter.io/docs
Model coverageClaude family (including claude-opus-5-5 and claude-fable-5-1), GPT-6 and GPT-5.6, Gemini 3.8 Flash, plus DeepSeek, GLM, MiniMax, Moonshotrelayrouter.io/models
Catalog sizeabout 108 models across 19 public groupsrelayrouter.io/models
Settlement ratesGPT group CNY 0.6 per $1 of standard usage, Claude group CNY 2.0, against a CNY 6.8 per $1 market referencerelayrouter.io/models
Direct pricingdeepseek-v4-flash is billed at 1.1x DeepSeek official time-of-day prices: off-peak CNY 1.1 per 1M input tokens and CNY 4.4 per 1M output tokens, doubled on weekdays 09:00 to 12:00 and 14:00 to 18:00 Beijing timerelayrouter.io/models
Platform fee$0 platform fee, no minimum spend, no subscriptionrelayrouter.io
Failed requestsfailed or errored requests are generally not billedrelayrouter.io

Data verified 2026-10-08; live prices are on the official /models page.


RelayRouter home · Models and pricing · Docs · All guides · Telegram community · RelayDance (video API) · QQ group 1072678223