Deploy Nash on your storefront in five minutes

This page walks through the hosted Nash onboarding flow at negotiate.pier39.ai/store_onboarding/start. It's for merchants who don't want to write code, install Python libraries, or read setup guides. You fill out a short form, drop in your product CSV, and walk away with a zipped Nash deployment bundle ready to run.

The whole flow takes about five minutes. Three of them are filling in form fields.


Before you start

You need three things ready:

  1. Your store name and city. Two text fields. That's it.
  2. A product CSV. Either a Shopify product export (Admin → Products → Export → CSV), or any spreadsheet you save as CSV with the columns id, name, list_price, category, description.
  3. An Anthropic API key. Get one at console.anthropic.com. Costs about $0.05–$0.15 per negotiation. The form doesn't store your key — it gets written into a file inside the zip you download, and never travels anywhere else.

The five steps

1. Open the form

Visit negotiate.pier39.ai/store_onboarding/start. You'll see a single page with three sections: Store, Products, API key.

2. Tell us about your store

Four short fields:

Field Example
Store name Atlas Premium Appliance
City Atlanta, GA
Sales rep name Casey (this is the persona the AI takes on when chatting with shoppers)
Tagline Authorized partner. Outlet pricing.

These show up in the chat agent's introduction and on your store's discovery file.

3. Get your products in

Two ways:

Option A — Connect Shopify (one click). If you run a Shopify store, click Connect Shopify at the top of the Products section. Enter your store domain (yourstore.myshopify.com), approve the read-products permission on Shopify, and your catalog gets pulled in automatically. No CSV export needed. The form picks back up where you left off, with all your products loaded.

Option B — Upload a CSV. Drag your file onto the dashed box (or click to browse). Two CSV shapes are accepted:

If your CSV mixes lifecycles (some outlet, some clearance, some open-box) you don't have to upload separate files. Generic CSVs use the per-row category column; Shopify exports auto-map any tag whose name matches a category, and you can override anything in the tag-mapping UI that appears after upload. Full details in the appendix.

4. Add your API key

Paste your ANTHROPIC_API_KEY (starts with sk-ant-...). If you haven't got one yet, leave this blank — the bundle will include a placeholder you can fill in later.

5. Build and download

Click Build my store bundle. The form does its work in about a second and your browser auto-downloads yourstore-negotiable.zip.

If anything goes wrong, you'll see a red error message right under the button (typically: "CSV had no usable rows" — usually means the file format dropdown doesn't match the file you uploaded).


What's inside the zip

yourstore-negotiable/
├── catalog.json    Your full product catalog with floors and concession levers per item
├── serve.py        Six-line script to start the merchant backend
├── .env            Your Anthropic API key (don't commit this to git)
├── Dockerfile      For deploying to any cloud
├── fly.toml        Pre-configured for Fly.io with your store-slug app name
└── README.md       Quickstart for running locally and deploying

Open catalog.json in a text editor before going live and check two things per product:


Run it locally

After unzipping, in your terminal:

cd yourstore-negotiable
pip install pier39-merchant-server
python serve.py

That starts the merchant backend on http://localhost:8000. In another terminal, sanity-check:

curl http://localhost:8000/negotiate.json
curl http://localhost:8000/api/store/catalog

If both return JSON, you're live. Try a chat session:

curl "http://localhost:8000/api/store/chat/start?product_id=<your-product-id>"

Deploy to the cloud

Fly.io is the fastest path. Three commands:

fly launch --no-deploy --copy-config --yes
fly secrets set ANTHROPIC_API_KEY=$(grep ANTHROPIC_API_KEY .env | cut -d= -f2)
fly deploy

That gets you a working URL like yourstore-negotiate.fly.dev. To use your own domain:

  1. At your DNS provider, add a CNAME from negotiate.yourstore.com (or whatever subdomain you want) to your Fly app.
  2. Run fly certs add negotiate.yourstore.com — Fly handles TLS automatically.

Render, Railway, or your existing app server work too — anywhere that runs Python and stays alive between requests.


Get found by AI shoppers

Once your store is live at a real domain, list yourself in the negotiate-directory at github.com/sanjana-pier39/negotiate-directory. It's a one-PR process — fork the repo, add an entry to registry.json, open the PR.

Within five minutes of the PR merging, every AI shopper using the protocol can find you. Their flow becomes:

"Find me a store that sells [your category] and negotiate."

→ Their AI 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.


What you can change later

Everything in the bundle is plain text. You can edit any of it after the fact:


Common questions

Do I have to use Fly.io? No. The bundle's Dockerfile works on any container host (Render, Railway, Google Cloud Run, AWS Fargate, your own VM). The fly.toml is just a convenience.

Can I use this without an API key? You can build and download the bundle without one, but the chat agent won't work until you set ANTHROPIC_API_KEY in .env or as a Fly secret.

Will the form save my data? No. The form is stateless. Your CSV, your store info, and your API key get processed in memory to build the zip, then thrown away.

What if my CSV has weird columns? Pick "Generic CSV" and make sure your file has at least id, name, list_price, category, description. Other columns are ignored. If your column names don't match, rename them in your spreadsheet before uploading.

What if my products don't fit any of the default categories? Pick the closest one as your default and let it apply to everything. After you download the bundle, edit catalog.json and adjust the floor and levers for any product that needs a tighter or looser policy.

Can I assign different categories to different products in one upload? Yes — three ways. Generic CSV: include a category column, each row gets its own value. Shopify CSV: use Shopify Tags whose names match category names (e.g. outlet, open-box, clearance) and they auto-route. For arbitrary Shopify tags (refurb, final-sale, etc.), the form shows a tag-mapping UI after you upload the CSV — point each tag at a category from a dropdown. See the Mixing categories section above for the full priority order.

How much will this cost me to run? Roughly $0.30 per six-turn negotiation in Anthropic API fees. A store handling 100 negotiations a day costs about $30/day. The protocol library and the directory are free; hosting costs depend on where you deploy (Fly.io's free tier is enough for low traffic).

Can I get help? Email sanjana@pier39.ai. Or open an issue at github.com/sanjana-pier39/pier39-skills.


Appendix — Categories and levers

This is the technical detail behind Step 3. Skip it for the basic flow; come back when you want to understand exactly what the form generated and how to tune it.

The six categories

Every product in your catalog is tagged into one of these "negotiation profiles." The profile sets two things: the floor (mathematically enforced lowest price) and which lever template the Nash seller agent uses for non-cash concessions.

Category Floor (% of list) Lever template When to use
premium_new 95% small_appliance_premium High-demand, low-discount items
standard_new 88% small_appliance_standard Regular new inventory
outlet 85% small_appliance_standard Standard outlet/overstock items
open_box 78% open_box Open-box / display units
aged_30plus 75% aged_inventory Items aged 30+ days
clearance 65% clearance End-of-life, get-it-out-the-door

Floor enforcement is in the agent's system prompt: it explicitly forbids closing below the floor and instructs the agent to walk away gracefully. The agent doesn't see customer pressure the way a human rep does, so the floor holds consistently.

How categories are assigned to products

The form uses three signals, in this priority order:

  1. Generic CSV — per-row category column. If you uploaded a generic CSV, each row's category cell is read directly. A single file can mix all six categories across products.
  2. Shopify CSV — explicit tag mapping. After uploading a Shopify export, the form lists every unique tag and lets you map each one to a category from a dropdown. Whatever you set here wins.
  3. Shopify CSV — auto-map by tag name. Tags whose names match a category (case-insensitive, hyphens/spaces normalized to underscores) auto-route. So outlet, clearance, open-box, aged-30plus route automatically without any UI action.
  4. Default category dropdown — fallback. Anything not covered by the above falls through to whatever the dropdown is set to.

So a Shopify export with five products tagged outlet, open-box, aged-30plus, clearance, and fancy-tag produces five products at five different floors with zero config — the first four auto-map; the fifth falls through to the dropdown default.

How levers are generated

Each category points to a lever template — a list of 4–7 strings, ordered cheapest-give to biggest-give. The Nash seller agent uses these top-down during a negotiation. The small_appliance_standard template (used by outlet and standard_new) looks like this:

1. "Free expedited 2-day shipping (~${ship_cost} retail value)"
2. "Bundle a free {accessory_name} (${accessory_price} retail) — currently overstocked"
3. "Extend {brand} warranty from {base_warranty_years} to {extended_warranty_years} years"
4. "Free year-supply of replacement supplies (${filter_price} retail)"
5. "${credit} credit toward any next order, valid 12 months"
6. "Free 12-month enrollment in our loyalty program"

The {variables} get filled per-product. For each product:

Variable Source Default if missing
{brand} Shopify Vendor column, or brand in generic CSV the manufacturer
{ship_cost} ship_cost column (generic CSV) 35
{accessory_name} accessory_name column (generic CSV) bonus accessory
{accessory_price} accessory_price column (generic CSV) 49
{filter_price} filter_price column (generic CSV) 55
{credit} credit column (generic CSV) 30
{base_warranty_years} base_warranty_years column 2
{extended_warranty_years} extended_warranty_years column 4
{city} city column your city

A Dyson V15 vacuum priced at $649 with the Shopify tag outlet ends up with these levers:

"levers": [
  "Free expedited 2-day shipping (~$35 retail value)",
  "Bundle a free bonus accessory ($49 retail) — currently overstocked",
  "Extend Dyson warranty from 2 to 4 years",
  "Free year-supply of replacement supplies ($55 retail)",
  "$30 credit toward any next order, valid 12 months",
  "Free 12-month enrollment in our loyalty program"
]

{brand} got Dyson from the Shopify Vendor column. The rest are package defaults — Shopify exports don't include accessory names, replacement-part prices, or credit policies, so those fields fall through.

Making levers store-specific

Two paths:

Edit catalog.json after download. Open the file in a text editor. Each product's levers is a plain JSON array of strings. Replace "bonus accessory" with whatever you'd actually bundle for that SKU, fix $49 retail to the real value, drop levers that don't apply, add ones that do. The agent rereads the catalog on every chat session, so changes take effect on next deploy. About five minutes per product if you're being thorough; less when batching.

Use a Generic CSV with extra columns. Switching the form's "CSV format" to "Generic CSV" lets you supply the variables directly per row. Columns the renderer recognizes:

id, name, list_price, category, description,
brand, accessory_name, accessory_price, filter_price,
ship_cost, credit, city,
extended_warranty_years, base_warranty_years,
in_stock, days_on_lot

Fill these once in your spreadsheet before exporting and every lever gets correctly substituted. Useful for stores with consistent accessory bundles (e.g., a bike shop where every bike comes with a free helmet — set accessory_name=helmet, accessory_price=85 in every row).


→ Open the form