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.


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:

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}"
}

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}"
}

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:

  1. The agent never quotes below the merchant's floor. The floor and the available concession levers are hidden merchant state and never leave the server.
  2. 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

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:

  1. Serves a valid descriptor at /negotiate.json and /.well-known/negotiate.json with negotiate_protocol: "nash.v1".
  2. Exposes start_chat, send_message, and read_history per §3, each returning a next link (or null at close).
  3. Never leaks hidden merchant fields (floor, levers, inventory_note).
  4. Enforces a per-IP rate limit on session creation.
  5. 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.