Test a bepleez negotiation from your backend
Developer quick start: open a negotiation, counter-offer, accept and verify the agreement token, without Shopify.
What you are building
The example code (MCP and A2A, MIT license) is in the public repository bepleez/bepleez-for-dev.
Your backend calls bepleez's seller agent through an MCP server (JSON-RPC over HTTPS). The agent negotiates a price with your shopper within the limit you set, then returns a signed agreement token that your backend verifies before applying the price. The price displayed on your site does not change.
You need your agent's MCP address and a test key, both in your dashboard (dash.bepleez.com), My shop menu, Integration tab. You generate the key yourself; it is shown only once.
Rules to know
- The product record comes from your backend, never from the shopper's browser. It is frozen when the negotiation opens: a new price needs a new negotiation.
- The seller states nothing that is not in
description(new, warranty, accessories…). Put the facts it may state there. - A missing price or a price of 0 is refused (
product_price_missing): bepleez never invents a price. - Unit prices, quantity 1, in the record's currency.
- Test plan: 100 negotiations in total, 50 per day (adjustable on request). Beyond that:
quota_exceeded; running negotiations continue. - You see the acts exchanged, never the seller's internal reasoning.
1. Configure
export BEPLEEZ_MCP_URL="https://api.bepleez.com/agents/<id>/mcp" # shown in Integration
export BEPLEEZ_API_KEY="bpz_test_..." # never put it in a browser
Every call carries the header Authorization: Bearer $BEPLEEZ_API_KEY. Any authentication failure returns the same 401 invalid_api_key.
2. Open a negotiation
Tool request_offer. A seller turn takes 9 to 17 s (up to 60 s for the opening): pass async: true to get status: "in_progress" at once, then poll get_thread until status: "ready".
curl -s "$BEPLEEZ_MCP_URL" \
-H "Authorization: Bearer $BEPLEEZ_API_KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"request_offer","arguments":{
"product":{"sku":"DEMO-1","title":"18V cordless drill","price":129.9,"currency":"EUR",
"description":"New, 2-year warranty, 2 batteries and a charger.","stock":40},
"message":"Hi, I need a drill for work this weekend, my budget is €110.",
"idempotency_key":"7d1f0c2e-3b9a-4a51-9a6e-0c1d2e3f4a5b","async":true}}}'
The result (in structuredContent) contains thread_id, status, state, seller_messages (the seller's words) and current_offer: offer_id, binding (firm or indicative), total, valid_until, and options if the seller proposes a menu.
3. Counter-offer
Tool counter_offer with thread_id, message (the shopper's words, including a price if they name one) and a mandatory idempotency_key. After any timeout, resend the call with the same key: nothing is repeated and you get the same result (replayed: true).
An indicative offer cannot be accepted; only a firm one can. If the seller answers with a menu (options), present every option on an equal footing and keep the chosen option_id.
4. Accept
Tool accept_offer, only after the shopper explicitly agrees: thread_id, offer_id and, for a menu, option_id. The response carries agreement: deal_token (RS256 JWT), jwks_url, verify_url, price, currency, sku, expires_at. discount.code is only a reference: no coupon is created in your system.
5. Verify the agreement token
Before charging the negotiated price, your backend verifies the token with the public key at jwks_url (RS256 signature, expiry, expected sku and price), or calls verify_url. Never trust a price sent by the browser.
Errors
An error arrives with isError and structuredContent.messages[0].code:
product_missing,product_price_missing,product_invalid: product record missing or wrong.quota_exceeded: daily limit or test plan reached.idempotency_key_required,invalid_arguments: malformed call.turn_in_progress: the seller is still answering; pollget_thread, then retry.unknown_thread: this negotiation is not yours.offer_not_acceptable,option_required,offer_expired: no firm offer, option to specify, or offer expired (re-readget_thread).no_active_strategy: no active strategy for this product, contact us.seller_refused,seller_unavailable: the seller refused, or a temporary failure (retry with the same key).
The contract version is in every response (contract_version, currently 2026-11-02).
Complete examples
The bepleez-for-dev repository holds two sample apps in Python 3, no dependency:
git clone https://github.com/bepleez/bepleez-for-dev && cd bepleez-for-dev
python3 quickstart/python/app.py --auto --budget 118 # MCP: full negotiation in the terminal
python3 a2a/app.py --budget 118 # A2A: same steps, the seller's signatures are verified
They read only BEPLEEZ_MCP_URL (or BEPLEEZ_A2A_URL) and BEPLEEZ_API_KEY, like your own backend. The repository also has agreement-token verification in Python and Node.
MCP or A2A
Both use the same key and the same agent. MCP is the simpler contract (structured results, coded errors, async mode, idempotency key). A2A fits if your agents already speak the protocol: you send acts (request, counter, accept), the product record goes in message.metadata["com.bepleez.shopping.product"], and the proof of agreement is a signed commit act (ES256), verified with the keys of the seller's UCP profile.
For agents
The UCP profile (/agents/<id>/.well-known/ucp) and the A2A agent card describe automatic discovery; a summary for agents is published in llms.txt.
A question, or stuck? Write to us: hello@bepleez.com