Skip to content

Developer docs

Build against the arbiter.

Three roles, nine message types, one signed receipt. This page is the contract: the session flow your agent follows, the transports it can use, and the receipt format you can verify today without asking us anything.

Session flow

Nine message types carry the whole session.

Every message is a JSON object with a session id, a sender role, a type, and a payload. The arbiter sees them all; the parties see their own, the arbiter’s, and what the arbiter relays.

Buyer agent

Opens the session, commits to its private ceiling as a hash, proposes and counters.

Merchant agent

Acknowledges the session, commits to its private floor as a hash, counters and accepts.

Arbiter

Never bids. Fixes the rules, scores every offer, rejects the ones outside the band, and signs the record.

Message types, who sends them, and what they carry
TypeFromCarries
session.openbuyerThe object, the buyer’s declared constraints, a hash commitment to its private ceiling, and the fairness profile.
session.ackmerchantA hash commitment to the merchant’s private floor and what it declares openly: the list price, the inclusions on the table.
session.acceptarbiterThe session rules: max rounds, round timeout, both commitments, the arbiter’s key id.
offer.proposebuyer or merchantRound number, price, inclusions, expiry.
offer.counterthe other partyThe same shape as a proposal.
offer.accepteither partyThe round being accepted; terms identical to the offer on the table.
arbitration.evaluatearbiterFair, unfair to the buyer, or unfair to the merchant, with the score and the price that would make the offer fair.
arbitration.rejectarbiterWhy an offer was refused, which invariant it broke, and the suggested price for the one corrective round.
session.agreearbiterFinal terms, the signed agreement artifact, and the next step for settlement.
session.closearbiterThe reason: rounds used up, a timeout, a party’s rejection, or a violated invariant.

Rounds are bounded, constraints are immutable once committed, and a concession cannot be taken back. The seven invariants the arbiter holds are listed on the home page; every receipt reports the state of each.

Transports

The envelope is the same on every wire.

Transports may add headers for auth and tracing. None may change the message.

HTTPS, JSON body
Request and response for agents that call the arbiter directly.
MCP tool call
The same messages as tool calls, for agents that already live inside an MCP client.
Server-sent events
Verdicts and closes streamed from the arbiter to both parties.

Messages carry their own ids. A duplicate id is processed once; a retry reuses the id it is retrying.

The receipt

What every session leaves behind, and how to check it.

A receipt is the session’s verdicts and invariants, the head of its hash chain, and the final terms signed by the arbiter with Ed25519. It verifies offline, with the public key it carries.

Fields

session_id · state · enforcement
Which session, how it ended (agreed or closed), and whether the arbiter enforced its verdicts or only recorded them.
rules · reference inputs
The session rules, and the reference inputs the arbiter fixed before the first offer: list price, the value of each inclusion, the target cost, and their source.
parties
Each side’s agent id, its declared constraints in the clear, and the hash commitment to its private limit.
arbiter
agent_id and relationship: independent, or affiliated when the arbiter and a party were the same interest.
verdicts · close · final_terms
Every round’s verdict with the score and outcome; the close reason if there was no agreement; the agreed terms if there was.
invariants · reveals
The state of each of the seven invariants, and whether each party’s revealed private limit matched its commitment.
chain
The head hash and length of the session log. Each envelope carries prev_hash, the sha256 of the canonical bytes of the one before it.
artifact · signed_payload
A detached Ed25519 JWS (EdDSA, b64 false, kid) and the exact payload it signs: the final terms plus the session metadata, including the chain head at signing.
jwks · signature
The arbiter’s public key, embedded by kid, and the arbiter’s own verification result for the reader to recompute.
mode · hosted
On the hosted gateway: test or live, and a disclosure of who ran the merchant side and who made the buyer’s commitment.

Canonical bytes

  • RFC 8785 canonical JSON: object keys sorted by code unit.
  • No whitespace between tokens.
  • Numbers are integers in minor units; fractions travel as strings, so serialization is unambiguous.
  • Hashes are sha256 over the canonical UTF-8 bytes, written sha256:<hex>.

Verifying

  1. 01Decode the JWS protected header: EdDSA, an unencoded detached payload (b64 false, crit b64), and a kid.
  2. 02Find the key with that kid in the embedded JWKS: an OKP Ed25519 key with its public point x.
  3. 03Rebuild the signing input, the protected header text, a dot, and the canonical bytes of signed_payload, and verify the Ed25519 signature.
  4. 04Confirm the receipt’s final_terms and session_id are the ones that were signed.
  5. 05With the session log in hand, recompute every prev_hash link and confirm the last envelope is the head the receipt names.

A real one

The receipt of a recorded session in which Valyzen ran both sides and the arbiter, so it is stamped affiliated. This excerpt leaves out the reference-input block and per-verdict inclusion detail; the verifier loads the receipt whole, and it verifies.

{
  "oanp_version": "0.1",
  "library_version": "0.2.0",
  "session_id": "01M185SPPV9E8MTA8M5XYP2JG6",
  "state": "agreed",
  "enforcement": "arbitrated",
  "fairness_profile": "default/v0.1",
  "rules": {
    "max_rounds": 5,
    "round_timeout_seconds": 120,
    "fairness_profile": "default/v0.1",
    "audit_retention_days": 90,
    "fair_band": "0.05"
  },
  "parties": {
    "buyer": {
      "agent_id": "valyzen-demo-buyer",
      "commitment": "sha256:f6fa6be1089cff8ff4af15fa0f8b1717a9c073bbe588b4c307d81ed49b845a00",
      "declared": {
        "max_rounds": 5,
        "currency": "INR",
        "inclusions": [
          {
            "kind": "meal",
            "value": "wanted"
          }
        ],
        "utilities": {
          "meal": "0.8",
          "baggage": "0.5"
        },
        "non_price_preferences": []
      }
    },
    "merchant": {
      "agent_id": "valyzen-demo-merchant",
      "commitment": "sha256:80c640fea26a328d3e920a40ed53d805a76be51777ce94f2c50cd40b45be8ff1",
      "declared": {
        "max_rounds": 5,
        "currency": "INR",
        "inclusions": [
          {
            "kind": "meal",
            "value": "included"
          }
        ],
        "utilities": {
          "meal": "0.2"
        },
        "non_price_preferences": []
      }
    }
  },
  "arbiter": {
    "agent_id": "valyzen-gateway-arbiter-dev",
    "relationship": "affiliated"
  },
  "rounds_completed": 1,
  "verdicts": [
    {
      "round": 1,
      "verdict": "unfair_to_merchant",
      "fairness_score": "1.1504",
      "w_minor": 28000,
      "t_minor": 452000,
      "t_target_minor": 520000,
      "currency": "INR",
      "price_minor": 480000,
      "outcome": "rejected"
    },
    {
      "round": 1,
      "verdict": "fair",
      "fairness_score": "1.0000",
      "w_minor": 28000,
      "t_minor": 520000,
      "t_target_minor": 520000,
      "currency": "INR",
      "price_minor": 548000,
      "outcome": "pending"
    },
    {
      "round": 1,
      "verdict": "unfair_to_merchant",
      "fairness_score": "1.2235",
      "w_minor": 7000,
      "t_minor": 425000,
      "t_target_minor": 520000,
      "currency": "INR",
      "price_minor": 432000,
      "outcome": "rejected"
    },
    {
      "round": 1,
      "verdict": "fair",
      "fairness_score": "1.0000",
      "w_minor": 7000,
      "t_minor": 520000,
      "t_target_minor": 520000,
      "currency": "INR",
      "price_minor": 527000,
      "outcome": "pending"
    },
    {
      "round": 1,
      "verdict": "fair",
      "fairness_score": "1.0000",
      "w_minor": 7000,
      "t_minor": 520000,
      "t_target_minor": 520000,
      "currency": "INR",
      "price_minor": 527000,
      "outcome": "accepted"
    }
  ],
  "close": null,
  "final_terms": {
    "price": {
      "amount_minor": 527000,
      "currency": "INR"
    },
    "inclusions": [
      {
        "kind": "meal",
        "value": "included"
      },
      {
        "kind": "baggage",
        "value": "included"
      }
    ]
  },
  "no_deal_welfare": {
    "currency": "INR",
    "agreed_price_minor": 527000,
    "bilateral_reference_price_minor": null,
    "buyer_no_purchase": false,
    "merchant_revenue_foregone_minor": null
  },
  "invariants": {
    "I1": {
      "name": "Bounded rounds",
      "status": "enforced",
      "detail": "bounded at 5 rounds; 1 used"
    },
    "I2": {
      "name": "Symmetric information to the arbiter",
      "status": "held",
      "detail": "both parties declared their constraints before any offer was scored"
    },
    "I3": {
      "name": "Constraint immutability within a session",
      "status": "verified",
      "detail": "every revealed constraint matched its commitment"
    },
    "I4": {
      "name": "Monotonic concession",
      "status": "enforced",
      "detail": "every offer was checked against that party's own previous offer"
    },
    "I5": {
      "name": "Fairness-score verdict per round",
      "status": "enforced",
      "detail": "5 evaluations, 2 rejected"
    },
    "I6": {
      "name": "Signed final artifact",
      "status": "signed",
      "detail": "Ed25519 detached JWS over RFC 8785 canonical bytes"
    },
    "I7": {
      "name": "Auditable history",
      "status": "chained",
      "detail": "15 envelopes, each linked to its predecessor"
    }
  },
  "reveals": {
    "buyer": true,
    "merchant": true
  },
  "chain": {
    "head": "sha256:87dc72656540888579b6b5ea8d95330ffba0e52dc8c8bcc6144239fadb6455c7",
    "length": 15,
    "retention_days": 90
  },
  "artifact": {
    "protected": "eyJhbGciOiJFZERTQSIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il0sImtpZCI6ImVkMjU1MTktZDQ5ZTk1MzdkNzczMjk5OCIsInR5cCI6Im9hbnAtYWdyZWVtZW50K2p3cyJ9",
    "signature": "lsRbOYS8YMKopP5Vdzu1INMyl53fq8pZUAuzpgiJihrVKpgxiFjUgOSt-9qRW7cczFECjYC1mHISfNRiT_puDw"
  },
  "signed_payload": {
    "final_terms": {
      "price": {
        "amount_minor": 527000,
        "currency": "INR"
      },
      "inclusions": [
        {
          "kind": "meal",
          "value": "included"
        },
        {
          "kind": "baggage",
          "value": "included"
        }
      ]
    },
    "session": {
      "session_id": "01M185SPPV9E8MTA8M5XYP2JG6",
      "oanp_version": "0.1",
      "arbiter_agent_id": "valyzen-gateway-arbiter-dev",
      "fairness_profile": "default/v0.1",
      "rounds_completed": 1,
      "chain_head": "sha256:ff94e1dde7e1d22dc27682e44100475178b4d533eeac6e4bf52e0af9cb942dfc",
      "buyer_commitment": "sha256:f6fa6be1089cff8ff4af15fa0f8b1717a9c073bbe588b4c307d81ed49b845a00",
      "merchant_commitment": "sha256:80c640fea26a328d3e920a40ed53d805a76be51777ce94f2c50cd40b45be8ff1"
    }
  },
  "jwks": {
    "keys": [
      {
        "x": "r74GdCrOjaO42MQ6q_Wksof0KpblklD8hCKmYC1lBQo",
        "kty": "OKP",
        "crv": "Ed25519",
        "kid": "ed25519-d49e9537d7732998",
        "alg": "EdDSA",
        "use": "sig"
      }
    ]
  },
  "signature": {
    "status": "valid",
    "reason": "signature verified"
  }
}

Sandbox and keys

Test keys, a hosted arbiter, and nothing billed.

Status

Follows the verifierOrder of arrival: verifier, then sandbox, then the Shopify connector.

The receipt format above is final and the verifier is live, so a receipt produced against the sandbox will verify the day it opens. Ask for early access and we will tell you where you are in the queue.

What you get

  • A test key and a live key. Test sessions carry mode: test in every receipt and cost nothing.
  • The arbiter over HTTPS and as MCP tools, with the same message types.
  • A sandbox buyer and a sandbox merchant, so one agent can be tested alone.
  • The arbiter’s public keys by key id, published at app.valyzen.ai/keys when the sandbox opens.

Capability

Declared by both sides, or not at all.

Agent commerce protocols let a store and an agent each declare what they support and use only what both declared. Valyzen publishes negotiation as one such capability.

The name

ai.valyzen.shopping.negotiation

Published under Valyzen’s own namespace, as the Universal Commerce Protocol allows any vendor to do. A store that declares it and an agent that declares it can open an arbitrated session between discovery and checkout. Neither side needs anyone’s approval, and neither is affected if the other does not declare it.

Published files

  • /ucp/agent-profile.json

    Agent profile. What the platform declares: services, capabilities, and the schema each one points at.

  • /ucp/negotiation/schema.json

    Negotiation capability schema. JSON Schema for the capability’s messages: the request that opens a session, the moves, and the session view.

Where it sits

  1. 01Discovery: the agent finds the product through the store’s own agent surfaces.
  2. 02Negotiation: both sides open a session with the arbiter; the receipt is signed.
  3. 03Settlement: the agreed price reaches the store’s checkout; the paid order closes the loop.

The endpoint behind the capability opens with the sandbox. Shopify merchants do not need to read the schema: the connector declares it for them.