Ahmed Mostafa ← Work
Work Sample · PRD Excerpt

The Zero-Friction Amazon Listing Wizard

Freelance client engagementPRD v2.0May 2026SP-API aligned11 steps26 pages

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.

About this excerpt

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:

Rule 1 · Input

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.

Rules 2 & 3 · Boundaries

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.

Rule 5 · Validation

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).

Rule 7 · Errors

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.

Rule 8 · Copy

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.

≥ 70%Step 1 → 11 completion
≤ 5 minMedian active session
≤ 25Free-text keystrokes
≥ 90%First-attempt acceptance

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.

StepOperationAPI · VersionMethod · Path
2 · ConnectgetMarketplaceParticipationsSellers · v1GET /sellers/v1/marketplaceParticipations
3 · Product typesearchDefinitionsProductTypesProduct Type Defs · 2020-09-01GET /definitions/2020-09-01/productTypes
4 · SchemagetDefinitionsProductTypeProduct Type Defs · 2020-09-01GET /definitions/2020-09-01/productTypes/{productType}
4 · EligibilitygetListingsRestrictionsListings Restrictions · 2021-08-01GET /listings/2021-08-01/restrictions
6 · BenchmarkgetCompetitivePricingProduct Pricing · v0GET /products/pricing/v0/competitivePrice
6 · FeesgetMyFeesEstimateForSKUProduct Fees · v0POST /products/fees/v0/listings/{SellerSKU}/feesEstimate
10 · Dry runputListingsItem mode=VALIDATION_PREVIEWListings Items · 2021-08-01PUT /listings/2021-08-01/items/{sellerId}/{sku}
11 · SubmitputListingsItemListings Items · 2021-08-01PUT /listings/2021-08-01/items/{sellerId}/{sku}
Post-submitgetListingsItemListings Items · 2021-08-01GET /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.

#StepWhat the user doesWhat the system does
1WelcomeTaps onceOpens a session, sets the "about 5 minutes" expectation
2Seller connectConfirms, or picks a marketplaceLWA refresh; filters participations to isParticipating=true; auto-selects after 1.2 s if there's only one
3Product typeTaps one of ≤ 12 icon cardsPre-fetches and caches the candidate product types, pre-loads step 4
4Category & compliancePicks from a searchable listFetches the JSON Schema and runs the restriction check in parallel; chip resolves in under 1.5 s
5Identifiers & basicsToggles condition, steps quantity, optionally types a barcodeLocal checksum on UPC-A / EAN-13 / GTIN-14; sets the GTIN exemption flag when there's no barcode
6PricingDrags a slider or taps Budget / Recommended / PremiumDerives the band from competitive pricing; live fee and payout preview on every change
7ImagesDrops 1–7 filesValidates format, size, dimensions; queues optional AI enhancement per slot
8Brand & styleOptionally types a brand; taps one of four style cardsComposes the generation context; pre-warms generation so step 9 renders instantly
9AI previewAccepts, regenerates, or edits one blockGenerates title, exactly 5 bullets, description and keywords, validated against the schema before display
10Validation previewResolves blocking issuesSends the real payload to Amazon in dry-run mode
11SubmitTaps onceSubmits, 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.

Issues path, rendered verbatim
{
  "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:

ScenarioRequired behaviour
LWA returns invalid_grantBlock 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 RESTRICTEDBlock Next and render Amazon's own reasons[].message as the explanation. Offer a route back to step 4.
Category needs APPROVALWarn but don't block. Carry the approval link through to the final summary.
AI generation times outRetry 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 schemaAuto-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 existsHide the suggestion chips rather than invent a band. Show the raw slider and say plainly that there's no benchmark yet.
Amazon returns 429Exponential backoff 1→2→4→8 s, four retries, then a calm message. Never lose the payload.
Submit succeeds, our DB write failsRetry 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 leftThe submission worker updates the row. The badge is correct on next entry. No lost state.
User abandons mid-flowSession 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.

PrerequisiteOwnerWhy it can't wait
SP-API Developer ProfileOperations / LegalNeeds a Professional selling account and Data Protection Policy compliance forms.
SP-API roles approvedCompliance / PlatformRequires screencasts demonstrating each role's use case. This is a review queue, not a task.
Appstore listingProduct / MarketingRequired before any third-party seller can authorise the app at all.
Product type groups CSVProduct / OpsMaps the 12 user-facing icons to SP-API keyword sets. Curated by a human, not generated.
Style preset prompt templatesContent StrategyEmbedded in the AI prompts and reviewed by Legal for banned and regulated terms.
Copy, microcopy, nudgesContent / DesignDelivered as an i18n bundle. The build does not author user-facing copy.
Legal review of restricted categoriesLegalAmazon enforces this technically, but our phrasing of a rejection still has to be accurate.
QA seller profilesQAThree 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.
  • AmazonService and AIService each 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.