The Zero-Friction Amazon Listing Wizard
A first-time seller has a product and no idea what a product type taxonomy is. The spec's job is to get them from that to a live Amazon listing in about five minutes and under 25 keystrokes. Below is how the document is built, and the four decisions in it I'd defend hardest.
Structure, rules, tables and payload shapes are as written. The full document runs 26 pages with the complete step-by-step breakdown, data model, and all 23 edge cases. Client name and commercial terms are omitted. Everything technical here references Amazon's public Selling Partner API, so nothing in the excerpt is proprietary to the client.
01Rule Zero: the section that outranks the document
Section 0 of the PRD is eight non-negotiable system rules, and it opens with the sentence that makes the rest of the document work:
If any later section appears to conflict with these rules, the rule wins. Engineering must raise the conflict, not silently resolve it.
A spec that doesn't say which section wins when two sections disagree has outsourced its hardest decision to whoever reads it last. Twenty-six pages will contradict themselves somewhere. Naming the tiebreaker up front is cheaper than discovering the contradiction in code review, and far cheaper than discovering it in production.
The rules themselves are mostly about keeping options open:
No free-text input unless this document explicitly authorises it. Every input is a tap, toggle, slider, dropdown, or pre-filled selection. The only authorised free text in MVP: Brand Name (optional, ≤ 50 chars) and inline edits to AI copy the user chooses to open.
The AI provider lives behind a single AIService interface; no vendor SDK is imported outside /services/ai/providers/. Amazon lives behind a single AmazonService; no raw Amazon endpoint is hit outside /services/amazon/. Both are swappable by environment variable.
Validation is schema-driven. The wizard does not hardcode required attributes for any product type. It pulls them from getDefinitionsProductType at runtime and caches per (productType, marketplaceId, productTypeVersion).
Listings issues returned by SP-API are surfaced inline using Amazon's own message text. The wizard does not translate or invent error text. It renders issue.message and links to the affected field via issue.attributeNames.
All launch copy passes an 8th-grade readability check in CI. Every screen is localisable. Currency, locale and date format come from the seller's active marketplace, never from a constant.
02The Typing Budget
The wizard has a budget of 25 keystrokes for the entire session, and every field has to compete for them.
That is a success metric in the document, not a slogan: median free-text keystrokes per session ≤ 25. Writing it as a number is what makes Rule 1 enforceable. "Minimise typing" is a preference and loses every argument. A measured ceiling is a constraint, and constraints are what hold when the deadline arrives.
The fifth metric is the one people skip, and it's a test rather than a number: the provider-swap drill. AIService and AmazonService must each be swappable or sandboxable via an environment variable with zero code change inside /wizards/. If that drill fails, Rules 2 and 3 were decorative.
03The capability matrix
Every wizard action that touches Amazon is pinned to an exact operation, API version, and path. Engineering uses these, not equivalents and not whatever the SDK autocompletes.
| Step | Operation | API · Version | Method · Path |
|---|---|---|---|
| 2 · Connect | getMarketplaceParticipations | Sellers · v1 | GET /sellers/v1/marketplaceParticipations |
| 3 · Product type | searchDefinitionsProductTypes | Product Type Defs · 2020-09-01 | GET /definitions/2020-09-01/productTypes |
| 4 · Schema | getDefinitionsProductType | Product Type Defs · 2020-09-01 | GET /definitions/2020-09-01/productTypes/{productType} |
| 4 · Eligibility | getListingsRestrictions | Listings Restrictions · 2021-08-01 | GET /listings/2021-08-01/restrictions |
| 6 · Benchmark | getCompetitivePricing | Product Pricing · v0 | GET /products/pricing/v0/competitivePrice |
| 6 · Fees | getMyFeesEstimateForSKU | Product Fees · v0 | POST /products/fees/v0/listings/{SellerSKU}/feesEstimate |
| 10 · Dry run | putListingsItem mode=VALIDATION_PREVIEW | Listings Items · 2021-08-01 | PUT /listings/2021-08-01/items/{sellerId}/{sku} |
| 11 · Submit | putListingsItem | Listings Items · 2021-08-01 | PUT /listings/2021-08-01/items/{sellerId}/{sku} |
| Post-submit | getListingsItem | Listings Items · 2021-08-01 | GET /listings/2021-08-01/items/{sellerId}/{sku} |
Three LWA roles are requested and no more: Product Listing, Pricing, and Inventory & Order Tracking. Brand Analytics, Tax, Buyer Communication and Direct-to-Consumer Shipping are explicitly not requested. Asking for scopes you don't use is how an approval review turns into a month.
Nothing about a marketplace is hardcoded. No marketplaceId literals, no currencies, no locales. All of it comes from the marketplace record returned at runtime. Multi-marketplace publish is Phase 2, and the payload builder is already array-ready for it.
04Eleven steps, because eleven is what was left
Strictly linear. Persistent progress header, persistent Back / Next / Save & Exit footer. The count comes from the same rule that produced the five-step Meta wizard: a wizard has as many steps as there are decisions only the user can make. Everything else became a precondition check they never see.
| # | Step | What the user does | What the system does |
|---|---|---|---|
| 1 | Welcome | Taps once | Opens a session, sets the "about 5 minutes" expectation |
| 2 | Seller connect | Confirms, or picks a marketplace | LWA refresh; filters participations to isParticipating=true; auto-selects after 1.2 s if there's only one |
| 3 | Product type | Taps one of ≤ 12 icon cards | Pre-fetches and caches the candidate product types, pre-loads step 4 |
| 4 | Category & compliance | Picks from a searchable list | Fetches the JSON Schema and runs the restriction check in parallel; chip resolves in under 1.5 s |
| 5 | Identifiers & basics | Toggles condition, steps quantity, optionally types a barcode | Local checksum on UPC-A / EAN-13 / GTIN-14; sets the GTIN exemption flag when there's no barcode |
| 6 | Pricing | Drags a slider or taps Budget / Recommended / Premium | Derives the band from competitive pricing; live fee and payout preview on every change |
| 7 | Images | Drops 1–7 files | Validates format, size, dimensions; queues optional AI enhancement per slot |
| 8 | Brand & style | Optionally types a brand; taps one of four style cards | Composes the generation context; pre-warms generation so step 9 renders instantly |
| 9 | AI preview | Accepts, regenerates, or edits one block | Generates title, exactly 5 bullets, description and keywords, validated against the schema before display |
| 10 | Validation preview | Resolves blocking issues | Sends the real payload to Amazon in dry-run mode |
| 11 | Submit | Taps once | Submits, then keeps listening for Amazon's verdict |
Note step 9. The AI generates the copy and the user reviews it. They never author. And the attribute names the generator produces are the literal SP-API field names: item_name, bullet_point, product_description, generic_keyword. No translation layer between what the model writes and what Amazon receives, because a translation layer is a place for a mismatch to hide.
05Schema-driven, or: how a spec avoids expiring
Amazon's required attributes differ by category and by marketplace, and they change. A wizard that hardcodes them is correct on the day it ships and wrong from then on.
So the requirements are fetched at runtime and cached on (productType, marketplaceId, productTypeVersion, locale) with a 24-hour TTL, and the cache is force-flushed the moment Amazon returns an issue code beginning AMAZON_INVALID_ATTRIBUTE. The last clause is the one that matters: the system treats Amazon's rejection as the signal that its own cache is stale, and repairs itself.
The Definition of Done tests it directly: changing the JSON Schema returned by getDefinitionsProductType must be reflected in steps 9 and 10 with no code change.
06Three validation layers, and the one that matters
- Field level. On every keystroke. Quantity is an integer 0–9999. Price is decimal, above zero, at or below 99,999.99. Barcode checksums run locally.
- Step level. On Next, blocking navigation. Each of the eleven steps has a named required condition and a named behaviour on failure.
- Schema and dry run. At step 10.
The third one is the decision I'd defend hardest in the whole document. Before the real submission, the wizard builds the complete payload and sends it to Amazon with mode=VALIDATION_PREVIEW. That makes Amazon's own validator the source of truth instead of a local guess at what Amazon wants.
The spec says so explicitly: local validation is best-effort and may differ from SP-API's authoritative validation; from step 10 onward, the SP-API response wins. Most specs would have written a local validator and called it done. Local validators drift.
07Render the error, don't rewrite it
ERROR-severity issues block the user. WARNING-severity issues show a chip and let them proceed. Every issue's attributeNames array becomes an Edit deep-link back to the step that owns that field, so the user lands on the thing they need to fix instead of being told to go find it.
{
"sku": "{sku}",
"status": "INVALID",
"submissionId": "f8b5...",
"issues": [
{
"code": "90220",
"message": "The item_name attribute exceeds the maximum length of 200.",
"severity": "ERROR",
"attributeNames": ["item_name"],
"categories": ["INVALID_ATTRIBUTE"]
},
{
"code": "5665",
"message": "Brand name is required for this product type.",
"severity": "WARNING",
"attributeNames": ["brand"]
}
]
}
Rewriting Amazon's error text feels like kindness. It is a second source of truth that drifts from the first one, and when a seller pastes your friendly message into a support forum, nobody can find it.
08Twenty-three edge cases
Section 8 enumerates 23 failure scenarios, each with required behaviour. The Definition of Done requires all 23 to be covered by automated tests. A sample of the ones that shaped the design:
| Scenario | Required behaviour |
|---|---|
LWA returns invalid_grant | Block Next. Deep-link to Seller Central authorisation. Preserve all wizard state for resume, so an expired token doesn't cost the user their work. |
| Category is RESTRICTED | Block Next and render Amazon's own reasons[].message as the explanation. Offer a route back to step 4. |
| Category needs APPROVAL | Warn but don't block. Carry the approval link through to the final summary. |
| AI generation times out | Retry once after 2 s. After two failures, degrade: the user types one line, and title and bullets fall back to schema-safe placeholders. The wizard never becomes unusable because the model is down. |
| AI output breaks the schema | Auto-trim to the limit. If that can't fix it, regenerate once silently, then surface it. Two attempts to fix it invisibly, then tell the user. |
| No pricing benchmark exists | Hide the suggestion chips rather than invent a band. Show the raw slider and say plainly that there's no benchmark yet. |
| Amazon returns 429 | Exponential backoff 1→2→4→8 s, four retries, then a calm message. Never lose the payload. |
| Submit succeeds, our DB write fails | Retry three times with backoff, then page on-call. The listing exists on Amazon's side; the system must reconcile via getListingsItem on the next session rather than pretend it never happened. |
| Status notification arrives after the user has left | The submission worker updates the row. The badge is correct on next entry. No lost state. |
| User abandons mid-flow | Session marked ABANDONED after 24 h. "Resume draft" restores the exact step. |
Read them as a group and the pattern is one rule: every failure keeps the user's work and tells them the truth.
09The Out-of-Code List
Every spec contains work no engineer can do. Name it and give it an owner, or it turns up as a surprise in launch week.
Section 11 is fourteen prerequisites, each assigned. It's the section I'd point at if someone asked what separates a PRD from a feature description.
| Prerequisite | Owner | Why it can't wait |
|---|---|---|
| SP-API Developer Profile | Operations / Legal | Needs a Professional selling account and Data Protection Policy compliance forms. |
| SP-API roles approved | Compliance / Platform | Requires screencasts demonstrating each role's use case. This is a review queue, not a task. |
| Appstore listing | Product / Marketing | Required before any third-party seller can authorise the app at all. |
| Product type groups CSV | Product / Ops | Maps the 12 user-facing icons to SP-API keyword sets. Curated by a human, not generated. |
| Style preset prompt templates | Content Strategy | Embedded in the AI prompts and reviewed by Legal for banned and regulated terms. |
| Copy, microcopy, nudges | Content / Design | Delivered as an i18n bundle. The build does not author user-facing copy. |
| Legal review of restricted categories | Legal | Amazon enforces this technically, but our phrasing of a rejection still has to be accurate. |
| QA seller profiles | QA | Three sandbox accounts: empty, single-marketplace, multi-marketplace. The three shapes that break things. |
None of these appear on an engineering board. All of them can stop a launch. A spec that lists only what to build is a spec that will be late for reasons it never mentioned.
10Calm by default
Section 9.5 is four lines long and it's a product position, not a style note:
No red dots. No "x days left". No urgency manipulation.
Progress is charcoal on light and doesn't flash. Loading states say what is happening: "Asking Amazon to double-check…", then "Submitted, waiting for Amazon to confirm…". Confetti fires exactly once per user, on their first accepted listing, and never again.
Someone listing their first product is already anxious. Manufacturing more anxiety to drive a metric would work, briefly. Writing the prohibition into the spec is how you stop it being reintroduced by whoever owns activation next quarter.
11Definition of Done
Written as conditions that can be checked, not adjectives:
- A new user completes all eleven steps having typed 25 characters or fewer.
- A submitted listing is observable in Seller Central within Amazon's normal SLA, and our stored status reflects Amazon's final state, from either the synchronous response or the async notification.
AmazonServiceandAIServiceeach swap via environment variable with zero code change in the wizard.- All 23 edge cases are covered by automated tests, and every SP-API operation has a happy-path and an error-path integration test.
- Changing the schema Amazon returns changes wizard behaviour with no code change.
- No raw SP-API error reaches the UI. Every issue renders with severity, message, and an attribute-driven Edit link.
- All copy passes the readability check in CI. Currency and locale come from marketplace metadata.
And Phase 2 is pre-wired rather than promised: the status enum already reserves DELETED, the payload builder already accepts an array of marketplaces, the fulfilment channel is already parameterised for FBA, and patchListingsItem is already on the interface for the future Edit flow. Phase 2 hooks cost nothing at the time and save a rewrite later. Leaving them out is a decision, and usually the wrong one.
The rules at the top of a spec are the only part that survives the argument at the bottom of it.