nash.v1 — Protocol Specification
nash.v1 is an open HTTP/JSON protocol for AI-mediated commerce. It lets any AI
shopper agent (Claude, ChatGPT, a custom bot) discover a store, browse its
products, open a conversational session with the store's seller agent, and
negotiate to a close — all over plain HTTP, with no client-side URL construction
required.
This document is the formal spec. For a plain-language overview see OVERVIEW.md; to deploy a compliant store see STORE_SETUP.md.
- Protocol version string:
nash.v1 - Transport: HTTP/1.1, JSON bodies, UTF-8
- Default verb:
GET(POST equivalents exist for browser widgets) - Style: hypermedia-driven — every session response carries a
nextURL, so shoppers follow links instead of building them from templates.
1. Design goals
GET-based by default. Every step of a negotiation can be driven with a plain GET. This means even AI fetch tools that cannot issue POST requests can complete a full transaction. POST equivalents exist for browser-embedded widgets that prefer them.
Hypermedia-driven. The shopper never has to assemble a URL from a template
mid-session. Each response includes a next field containing the fully-formed
URL of the only valid next request. When next is null, the session is over.
Brand stays in control. The store's seller agent only ever offers terms the merchant pre-authorized in its catalog. Hidden merchant state (walk-away floor, concession levers, inventory notes) is never exposed over the wire.
Discoverable. A single well-known descriptor advertises everything an agent needs: the product list, the endpoint templates, and the limits.
2. Discovery
Every compliant store serves a JSON descriptor at a well-known path:
GET /negotiate.json
GET /.well-known/negotiate.json (mirror, identical body)
A store may additionally advertise itself in two crawler-friendly formats:
GET /llms.txt # human/agent-readable summary + protocol pointer
GET /robots.txt # allows the discovery files, disallows chat internals
GET /sitemap.xml # lists the descriptor, catalog, and product pages
2.1 Descriptor schema
{
"negotiate_protocol": "nash.v1", // protocol version string
"store": {
"name": "Vintage Supply Co",
"city": "San Francisco",
"rep_name": "Nash", // the seller agent's display name
"tagline": "Curated mid-century pieces",
"policy": "Free returns within 30 days"
},
"merchant_skill": {
"name": "pier39-merchant",
"repo": "https://github.com/sanjana-pier39/pier39-skills",
"description": "Seller agent with the pier39-merchant skill loaded."
},
"endpoints": {
"start_chat": {
"method": "GET",
"url_template": "https://{host}/api/store/chat/start?product_id={product_id}",
"params": { "product_id": "string, required, one of products[].id" }
},
"send_message": {
"method": "GET",
"url_template": "https://{host}/api/store/chat/{session_id}/say?message={url_encoded_message}"
},
"read_history": {
"method": "GET",
"url_template": "https://{host}/api/store/chat/{session_id}"
},
"catalog": {
"method": "GET",
"url": "https://{host}/api/store/catalog"
}
},
"products": [
{
"id": "eames-lounge-01",
"name": "Eames Lounge Chair",
"subtitle": "Herman Miller, walnut",
"list_price": 4200,
"currency": "USD",
"kind": "product",
"image_url": "https://{host}/img/eames-lounge-01.jpg",
"page_url": "https://{host}/store/p/eames-lounge-01",
"start_chat_url": "https://{host}/api/store/chat/start?product_id=eames-lounge-01"
}
],
"limits": { "currency": "USD" }
}
Notes:
negotiate_protocolMUST equalnash.v1for a compliant store. (The field is namednegotiate_protocolfor backward compatibility;nash.v1is the value.)products[]in the descriptor is the public view. It never contains the merchant's hidden fields (floor,levers,inventory_note).- Prices are integers in the major unit of
currency(e.g.4200= $4,200.00 whencurrencyisUSD). Nash operates inUSDby default; regional deployments may advertiseGBPorEUR.
2.2 Public catalog
For agents that want the full public product detail (descriptions, condition, etc.) without opening a session:
GET /api/store/catalog
Returns { "store": {...}, "products": [...] } with the same fields as the
catalog file minus the hidden merchant fields.
3. Session lifecycle
A negotiation is a short-lived server-side session identified by a session_id.
3.1 Start a session
GET /api/store/chat/start?product_id={product_id}
Response:
{
"session_id": "sess_abc123",
"greeting": "Hi! You're looking at the Eames Lounge — happy to help. …",
"product": { "id": "eames-lounge-01", "name": "Eames Lounge Chair", "list_price": 4200 },
"next": "/api/store/chat/sess_abc123/say?message={url_encoded_message}"
}
nextis the URL template for the shopper's first message. The shopper substitutes its URL-encoded message for{url_encoded_message}.- Starting a session is rate-limited per client IP (see §5).
3.2 Send a message
GET /api/store/chat/{session_id}/say?message={url_encoded_message}
Response:
{
"session_id": "sess_abc123",
"reply": "I can do $3,950 and include free white-glove delivery. …",
"closed": false,
"next": "/api/store/chat/sess_abc123/say?message={url_encoded_message}"
}
- The shopper repeats this call, following
nexteach time, until the seller agent ends the negotiation. - When the session reaches a natural conclusion (deal accepted or shopper walks
away), the response has
"closed": trueand"next": null.
3.3 Read history
GET /api/store/chat/{session_id}
Returns { "session_id": ..., "history": [ { "role": "user"|"assistant", "content": "…" }, … ] }.
The internal system priming message is not included in history.
3.4 POST equivalents (browser widgets)
Browser-embedded widgets that prefer POST may use:
POST /api/store/chat/start body: { "product_id": "…" }
POST /api/store/chat/{session_id}/message body: { "message": "…" }
These return the same response shapes as their GET counterparts. GET remains the canonical, spec-required surface; POST is an optional convenience.
4. Negotiation semantics
The seller agent is a brand-voiced LLM primed with the merchant's catalog and the
pier39-merchant skill. Two rules are guaranteed by the protocol:
- The agent never quotes below the merchant's
floor. The floor and the available concessionleversare hidden merchant state and never leave the server. - The agent trades, it doesn't give. Concessions are routed cheapest-first
in a decreasing-concession pattern defined by the merchant's
levers.
A shopper agent should treat the reply text as the authoritative offer and parse
any price/terms from it. There is no separate machine-readable "current offer"
field in nash.v1; the conversation is the interface.
5. Limits & errors
- Rate limiting.
start_chatis limited per client IP (the reference server defaults to a fixed number of new sessions per hour). Exceeding it returns429with{ "error": "rate limit: N/hour" }. - Missing/invalid product.
400with{ "error": "missing product_id" }or404for an unknown id. - Unknown route.
404with{ "error": "not found" }. - Server/catalog error.
500with{ "error": "catalog: …" }.
All error bodies are JSON with an error string. All endpoints send permissive
CORS headers so browser widgets can call them cross-origin.
6. Compliance checklist
A store is nash.v1-compliant if it:
- Serves a valid descriptor at
/negotiate.jsonand/.well-known/negotiate.jsonwithnegotiate_protocol: "nash.v1". - Exposes
start_chat,send_message, andread_historyper §3, each returning anextlink (ornullat close). - Never leaks hidden merchant fields (
floor,levers,inventory_note). - Enforces a per-IP rate limit on session creation.
- Returns JSON
{ "error": ... }bodies on the status codes in §5.
The pier39-merchant-server library implements all of the above out of the box —
you supply a catalog.json and it serves a compliant store. See
STORE_SETUP.md.