Store Setup — Getting the Pier39 Seller Agent Live on Your Storefront
This guide walks you through everything a store has to do to make itself negotiable by AI shoppers using the negotiate.v1 protocol. End state: any AI shopper agent (Claude, ChatGPT, custom bots) can land on your domain, discover that you negotiate, and close a deal.
The core onboarding is 30 minutes for a developer who already has a hosting setup. The conceptual work — defining floors and concession levers — usually takes longer because it requires real merchant judgment, not just config.
Before you start
You'll need:
- An Anthropic API key. console.anthropic.com. The merchant agent makes one Claude API call per turn (~$0.05–$0.15 with claude-sonnet-4-6).
- Python 3.10+ on the machine that will host the chat backend.
- A way to run a long-lived Python service. Anywhere that holds open HTTP connections works: Fly.io, Render, Railway, your existing app server, or a VM. Serverless platforms with short timeouts (Vercel functions, AWS Lambda) are not ideal because chat sessions live in memory.
- A domain you control. The protocol relies on serving
/negotiate.jsonat your store's domain. - A product list with real prices and your real walk-away numbers. This is the merchant judgment piece — see Step 1.
Step 1 — Build your catalog
The catalog is a single JSON file. For each product, you provide three public fields (the shopper sees these) and three private fields (only the merchant agent sees them):
| Public | Private |
|---|---|
name, subtitle, description |
floor — the lowest price you'd accept |
list_price |
levers — 5–7 non-cash concessions, cheapest first |
condition, year_made |
inventory_note — short context (urgency, manager latitude) |
Minimum example:
{
"store": {
"name": "Your Store",
"city": "Your City, ST",
"rep_name": "Casey",
"tagline": "Short tagline.",
"policy_default": "Free shipping. 30-day return. Manufacturer warranty.",
"about": "1-2 sentence story about your store."
},
"products": [
{
"id": "widget-pro",
"kind": "widget",
"name": "Widget Pro",
"subtitle": "Short marketing line",
"description": "1-2 sentence product description.",
"year_made": 2024,
"condition": "Brand new",
"list_price": 199,
"floor": 165,
"levers": [
"Free expedited shipping (~$15 retail)",
"Bonus accessory kit ($29 retail)",
"Extend warranty 12 → 24 months",
"$30 future-order credit, valid 12 months",
"Free setup video and quick-start kit"
],
"inventory_note": "5 in stock. Aged 14 days. Standard latitude."
}
]
}
The three private fields are the merchant judgment piece — see the Appendix for how to set floors, design levers, write inventory notes, and how to scale this to hundreds or thousands of products without doing it by hand.
A complete two-product working example lives at pier39-merchant-server/examples/tiny_store/catalog.json.
Step 2 — Install the merchant skill
The library auto-discovers the skill at ~/.claude/skills/pier39-merchant/. Get it there:
git clone https://github.com/sanjana-pier39/pier39-skills /tmp/pier39-skills
mkdir -p ~/.claude/skills
cp -R /tmp/pier39-skills/plugins/pier39/skills/pier39-merchant ~/.claude/skills/
Verify it landed:
ls ~/.claude/skills/pier39-merchant/SKILL.md
# should print the path with no error
If you can't put it at ~/.claude/skills/, you can put it anywhere and set PIER39_MERCHANT_SKILL_PATH=/path/to/pier39-merchant instead.
Step 3 — Install and run the server
pip install pier39-merchant-server
export ANTHROPIC_API_KEY=sk-ant-...
Then a 6-line Python script — call it serve.py, put it next to your catalog.json:
from pier39_merchant_server import serve
serve(
catalog_path="catalog.json",
host="0.0.0.0",
port=8000,
)
Run it:
python serve.py
You should see:
pier39-merchant-server listening on http://0.0.0.0:8000
Catalog: /path/to/catalog.json
Skill: /Users/you/.claude/skills/pier39-merchant
Model: claude-sonnet-4-6
(Ctrl-C to stop)
That's the entire backend. It serves all the endpoints the protocol requires — discovery file, catalog, chat — plus rate limiting, CORS, and OPTIONS preflight.
Step 4 — Verify the API end-to-end
In another terminal:
# 1. Discovery descriptor
curl -s http://localhost:8000/negotiate.json | python3 -m json.tool | head -10
# expect: "negotiate_protocol": "negotiate.v1"
# 2. Public catalog (hidden fields stripped)
curl -s http://localhost:8000/api/store/catalog | python3 -m json.tool
# 3. Start a chat session
curl -s "http://localhost:8000/api/store/chat/start?product_id=widget-pro"
# expect: {"session_id":"...","greeting":"Hi! I'm Casey...","next":"..."}
# 4. Send a shopper turn
SID=<paste session_id>
curl -s "http://localhost:8000/api/store/chat/$SID/say?message=Could+you+do+%24170+with+the+accessory+kit%3F"
# expect: {"message":"...merchant reply...","closed":false,"next":"..."}
If all four return real data and the merchant replies feel sane (anchored, conversational, holding the floor), you're done with backend setup.
Step 5 — Deploy to production
The server runs anywhere you can run Python. Three popular paths:
Fly.io (single command, ~5 min)
fly launch # answer the prompts, accept defaults
fly secrets set ANTHROPIC_API_KEY=sk-ant-...
fly deploy
Point your DNS at the Fly app, attach a TLS cert with fly certs add, and you're live.
Render
Add a new Web Service, point it at your Git repo, set the run command to python serve.py, and add ANTHROPIC_API_KEY as an env var. Render handles TLS automatically.
Your existing app server
The library is a stdlib ThreadingHTTPServer — you can run it next to your existing app on a different port and reverse-proxy /negotiate.json, /.well-known/negotiate.json, /llms.txt, and /api/store/* to it from your main nginx/Caddy config.
Don't use serverless
Serverless platforms like Vercel functions or AWS Lambda will work for the discovery files but break for chat — sessions are in-memory and instances cycle. If you need serverless, swap the in-process session store for Redis or DynamoDB.
Step 7 — Get listed in the public directory
Discoverability is a two-part problem. Above gets your store technically reachable by anyone who already knows your domain. Getting found by shoppers who don't yet know about you means listing in the public negotiate-directory at github.com/sanjana-pier39/negotiate-directory.
This is the registry the MCP connector's find_stores tool searches. Without an entry, a shopper saying "find me a store that sells widgets" won't find you even if your /negotiate.json is perfectly live.
Submission
It's a one-PR process. Fork the directory, add an entry to registry.json in alphabetical order, open a PR. Template:
{
"name": "Your Store Name",
"domain": "yourstore.com",
"tagline": "One-line description, under 80 chars.",
"city": "Your City, ST",
"categories": ["main-category", "sub-category"],
"products_count": 12,
"sample_products": [
"Most popular product",
"Second product",
"Third product"
],
"added": "2026-05-03"
}
Use the standard category tags listed in the directory's README so search works well across stores. The sample_products field is what makes free-text search actually useful — a shopper looking for "Aeron chair" matches your store because "Herman Miller Aeron Size B" is in your samples.
What happens after merge
Within 5 minutes of merge, every shopper using the negotiate-mcp connector worldwide can find you. Their flow becomes:
"Find me a store that sells [your category] and negotiate."
→ Claude calls find_stores(query=...) → gets your store back → calls start_negotiation(your_domain, product_id) → drives the negotiation through your chat agent.
No coordination with shoppers needed. The directory is the matchmaker.
Embed the compliance badge on your homepage
While you're polishing the listing, drop the negotiate.v1 badge into your storefront's footer. It's free brand reinforcement: real customers see it and learn the protocol exists, AI shoppers see the link and follow it back to the spec.
The badge is served from negotiate.pier39.ai so you don't have to host the SVG yourself. Two variants — pick the one that contrasts with your site background:
Light variant (white background with black border + text — embed on dark site backgrounds):
<a href="https://github.com/sanjana-pier39/pier39-skills">
<img src="https://negotiate.pier39.ai/badge.svg"
alt="negotiate.v1 compliant — AI agents welcome"
width="240" height="24">
</a>
Dark variant (black background with white text — embed on light site backgrounds):
<a href="https://github.com/sanjana-pier39/pier39-skills">
<img src="https://negotiate.pier39.ai/badge-dark.svg"
alt="negotiate.v1 compliant — AI agents welcome"
width="240" height="24">
</a>
Drop one of these into your store's footer template and you're done. See it live on the Atlas reference store — bottom-right of every page.
Markdown variant for READMEs
If you want to add the badge to your store's GitHub README, blog post, or any markdown surface:
[](https://github.com/sanjana-pier39/pier39-skills)
How to think about merchant judgment
The agent will faithfully execute the negotiation tactics in the pier39-merchant skill. What it can't do is decide for you what the floors and levers should be. A few heuristics worth applying:
- Don't set the floor where you'd be unhappy. The agent will close at the floor, frequently. If you'd be unhappy at that price, raise it.
- Set 5-7 levers per product, not 2. The decreasing-concession pattern only works if there's a ladder. With two levers, the agent runs out fast.
- Make the cheapest lever genuinely cheap. Free shipping, a $10 freebie, a code-for-next-order — these cost you almost nothing and feel valuable to the shopper. They should be the first concessions offered.
- Audit the transcripts. The chat sessions live in memory, but you can log them to disk by extending the library. Read the first dozen for each product, then tune the floor and lever order.
Common questions
How much will this cost in API spend? Sonnet 4.6 at current pricing: roughly $0.05 per shopper turn, so a 6-turn negotiation runs ~$0.30. A store handling 100 negotiations a day costs ~$30. Cap it with the per-IP and per-session limits in the library config.
Will the agent give away margin? Not below the floor you set. The agent's system prompt explicitly forbids going below floor and tells it to walk gracefully. The floor is enforced by the prompt, not by post-hoc validation, but Sonnet 4.6 + the merchant skill respects it consistently in our evals.
What if a shopper tries to manipulate the agent (prompt injection, etc.)? The agent treats every shopper message as input from a customer, never as instructions. Standard prompt-injection vectors are blocked at the system-prompt level. Rate limits (8 sessions/IP/hour, 30 messages/session, 2000 char/message) cap the blast radius if someone tries to abuse the API. For high-stakes deployments, add a separate moderation pass on shopper messages before forwarding.
Can I review what the agent said in any conversation?
Yes — GET /api/store/chat/<session_id> returns the running history. Sessions live for 1 hour by default, then are reaped. If you want long-term audit logs, add a hook in _post_to_chat to write each turn to your DB.
What happens if my Anthropic API key runs out of budget?
Calls to /api/store/chat/* will return 500 errors. Discovery files (/negotiate.json, /llms.txt) keep working since they don't call Claude. Set up Anthropic billing alerts.
Can the agent handle multiple products in one chat? Currently no — each session is bound to one product. If a shopper wants to negotiate over multiple items, they should start one session per product. (Multi-product bundling is on the roadmap.)
Does this work for B2B / quotes / large transactions?
Same protocol works for any value range. For very large transactions you'd want to add a "kick to human approval" path before the deal closes — easiest by intercepting closed=true events and routing to your sales team's queue.
Reference implementation
Everything described here is implemented and live at https://negotiate.pier39.ai/store — a fictional Dyson outlet. Check /negotiate.json to see a working descriptor:
curl -s https://negotiate.pier39.ai/negotiate.json | python3 -m json.tool
The full source is in this repo. Atlas is the canonical example any store can copy and adapt.
Appendix
The merchant-judgment fields in your catalog — floor, levers, inventory_note — are where setup actually takes time. This appendix explains how to choose them, plus how to generate them at scale when you have hundreds or thousands of SKUs.
Setting your floor
Your floor is the true lowest price you'd take. Not the listed price. Not a "slight discount." The number you'd genuinely walk away below. The merchant agent will hold it religiously — it's never tempted to cut deeper because it doesn't see customer pressure the way a human rep does. Common patterns:
- Outlet / overstock: floor at 80–88% of list (room to discount but margin protected).
- Used / consignment: floor at 75–85% of list, sometimes lower for aged inventory.
- Premium / scarce: floor at 92–96% of list (small room only).
- Service / experience-bundled: floor close to list because value is in the bundle, not the discount.
Rule of thumb: if you'd be unhappy closing at the floor, raise it. The agent will close there.
Designing levers
Levers are non-cash concessions the merchant gives instead of cutting price. Good levers cost you less than they're worth to the buyer. Examples that work in real chat:
- Free expedited shipping (real cost: $5–$10. Perceived value: $25–$50.)
- Bundle a $50-MSRP accessory you have overstocked (real cost: $5. Perceived value: $50.)
- Extend warranty from 12 to 24 months (real cost: marginal claims. Perceived value: clear.)
- Free setup, install, or training (real cost: an hour of staff time. Perceived value: high.)
- Future-purchase credit ($30 toward next order, valid 12 months — costs you nothing if unredeemed, locks in repeat).
- Manager-approved bonus (e.g., engraved gift box) that signals special handling.
The merchant agent uses these in decreasing-concession order — biggest perks first, smaller ones later — so the shopper feels they're winning each round even as the gap closes. Stack 5–7 levers per product. With only 2, the agent runs out of moves fast and either caves or walks too early.
Inventory notes
A free-text private field where you give the agent context like:
- "6 in stock; this one's the cleanest box. Aged 33 days; manager approved $80 latitude."
- "Only 1 in stock — single-owner provenance. Aged 12 days. Hold the line."
- "End-of-quarter; category bonus tier kicks in at one more sale."
The agent uses these to calibrate how hard to push. Without them, it defaults to standard tactics.
Building catalog.json for 100s or 1000s of products
Per-product hand-authoring of floor, levers, and inventory_note is fine for 10 products. For 100 it's tedious. For 1000 it's impossible. The repo includes a small toolkit at catalog-tools/ that replaces hand-authoring with category rules + lever templates + an LLM dry-run that catches mis-priced floors before you go live.
The full pipeline:
shopify_products.csv ─► shopify_to_products.py ─► products.csv
│
▼
build_catalog.py ─► catalog.json
│
▼
validate_catalog.py ─► report.json
│
▼
report_to_html.py ─► report.html
How it cuts the work down:
| Step | Time |
|---|---|
Export your products to a CSV (or use shopify_to_products.py) |
~10 min |
Write categories.json (5–15 categories, each with a floor_pct) |
~20 min |
Write lever_templates.json (5–10 templates, one per category) |
~1 hour |
Run build_catalog.py |
seconds |
Smoke-test with validate_catalog.py --limit 30 |
~5 min, ~$10 |
| Tune category rules from the report, rebuild | iterate 2–3× |
| Full validation pass on 1000 products | ~30–60 min, ~$300 |
Open report.html, click into any flagged transcripts |
as needed |
Each product in the validation report is tagged HEALTHY, EASY_GIVE, STUCK, CRITICAL, or ERROR. CI exits nonzero on any CRITICAL (closed below floor — should never happen). See catalog-tools/README.md for the full workflow with worked examples.
License
MIT for the protocol, library, and MCP connector. Apache 2.0 for the skills.