Beta

The Partner Trading API v1 is in beta — endpoints and response shapes may still change.

Developers
Cru World Wine
API v1 · REST & MCP
Getting started

Writes & confirmations

Every write — placing, changing, cancelling or renewing a bid; listing, changing or removing an offer; buying an offer or selling into a bid; saving or removing an inbond address — runs one of two ways, and the credential alone decides which. A partner’s write executes at once. A connected app’s write is staged, and executes only when the app confirms it after the customer says yes.

PARTNER CREDENTIALCONNECTED APP
SendsCLIENT-KEY, CLIENT-SECRETAuthorization: Bearer
A write answers201 or 200, done202, staged — nothing done
Safe to retry byThe reference in the bodyThe confirmation code
ThenNothing: it has happenedPOST /confirmations/{code} within 15 minutes

What can be written

ACTIONRESTMCP TOOLSCOPES
bid.place
Place a resting bid
POST /bidsplace_bidbids:write
bid.update
Change a bid’s price, quantity or options
PATCH /bids/{code}update_bidbids:write
bid.cancel
Cancel a bid
DELETE /bids/{code}cancel_bidbids:write
bid.renew
Put a bid back for 90 days
POST /bids/{code}/renewrenew_bidbids:write
bid.buy
Buy an offer at its asking price
POST /market/offers/{code}/buybuy_winebids:write orders:write
offer.list
List wine at a price you choose
POST /offerslist_for_saleoffers:write
offer.update
Change a listing’s price or quantity
PATCH /offers/{code}update_listingoffers:write
offer.remove
Take a listing off sale
DELETE /offers/{code}remove_offersoffers:write
offer.sell
Sell into a bid at its price
POST /market/bids/{code}/sellsell_to_bidoffers:write orders:write
inbond_address.add
Save an inbond address
POST /addresses/inbondadd_inbond_addressaddresses:write
inbond_address.remove
Remove an inbond address
DELETE /addresses/inbond/{id}remove_inbond_addressaddresses:write
inbond_warehouse.suggest
Suggest an inbond warehouse
POST /warehouses/inbondsuggest_inbond_warehousewarehouses:suggest

Over REST, POST /bids places a resting bid and POST /offers lists a line stored with Cru, wine held outside Cru or a supplier’s own stock, while POST /market/offers/{code}/buy buys an offer and POST /market/bids/{code}/sell sells into a bid straight away. The three inbond writes change no wine or money: they save or remove an inbond address, or suggest a warehouse, on your account. Suggesting needs warehouses:suggest, which Cru grants only to a partner credential, so a connected app never stages it.

The MCP tools follow the same rule: called with partner keys, each is carried out at once and takes a reference; with a connected app’s token, it stages, and confirm_instruction carries it out. With partner keys →

Partner credentials: at once, with a reference

  1. Your server

    Generate a reference and store it

    Up to 50 letters, digits and . _ : -, unique for your account for good. Save it with your own record of the order before anything goes over the wire.

    KEEPreference

  2. Your server

    Send the write

    POST/bids

    The reference goes in the JSON body — on DELETE too.

  3. Cru

    Cru executes it at once

    201 for something new — a bid, a listing, a purchase, an inbond address or a suggested warehouse — and 200 for the rest, with data.action, data.status: completed and the result.

    KEEPdata

  4. Your server

    No answer? Send it again, unchanged

    Cru replays the stored outcome with Idempotent-Replayed: true. While the first is still running you get 409 REQUEST_IN_PROGRESS: wait retry_after seconds and repeat.

The reference

Every write a partner credential sends must carry a reference in its JSON body — DELETE included. It is your idempotency key: Cru records the outcome against it, and any later request with the same reference gets that outcome back instead of being carried out.

Format 1–50 characters: a letter or digit, then letters, digits and . _ : -. Missing is VALIDATION_REQUIRED_FIELD; badly formed, VALIDATION_INVALID_VALUE.
Scope Your whole account, every endpoint, every credential you hold. Two partners can use the same string without colliding.
LifetimeForever. A reference is used once and never released.

What a repeat gets back

THE FIRST REQUEST…A REPEAT GETS
Completed The same status and body, with Idempotent-Replayed: true. Nothing is done again.
Is still running Cru waits up to five seconds for it, then answers REQUEST_IN_PROGRESS / 409 with details.retry_after. Repeat the identical request after that.
Was refused before it ran — a bad field, an untraded wine, a min_price or max_price the price misses, and most TRADE_REFUSEDNothing was recorded, so the reference is still free: a repeat is checked afresh.
Was refused while it ran — a TRADE_REFUSED or PRICE_CHANGED from the trade itself — or failed with INTERNAL_ERROR / 500The same answer, every time. That reference is spent.

A refusal does not say which of the two it was. After one, send the corrected request with a new reference: that is right either way.

Cru does not compare the bodies

A repeat is matched on the reference alone. Send a reference you have used before with a different body — or to a different endpoint — and you get the first request’s outcome, with no error to say so. Generate a fresh reference for every new instruction, and reuse one only for a retry of exactly the same request.

DELETE carries a body

DELETE /bids/{code}, DELETE /offers/{code} and DELETE /addresses/inbond/{id} need {"reference": "…"} as their JSON body. Some HTTP clients drop the body on DELETE unless told not to — check yours.

What a partner’s write returns

The message is Done; data.action names what ran, data.status is completed, and the rest is the result — the bid, the offer with the lines written, or what was removed. 201 for something new — a bid, a listing, a purchase, an inbond address or a suggested warehouse — and 200 for the rest.

201 · POST /bids, partner credential
{
  "status": "OK",
  "message": "Done",
  "meta": {
    "version": "1.0",
    "timestamp": "2026-10-19T10:10:55Z",
    "request_id": "6b183f2425c81699f1f3d3e5"
  },
  "data": {
    "action": "bid.place",
    "status": "completed",
    "bid": {
      "code": "BID-la4FCBHRAnKZ2vOaR8VF1KnHDYmFdaa_5N_mrFEFWzQ",
      "client_ref": "PO-2026-0193",
      "lwin18": "101187220240600750",
      "wine_name": "Lafite Rothschild 2024 (6x75cl)",
      "vintage": 2024,
      "pack_size": "6x75cl",
      "product_url": "https://markets.cruworldwine.com/uk/product/lafite-rothschild-2024-6x75cl",
      "price": 1650.5,
      "currency": "GBP",
      "bid_currency": "GBP",
      "quantity": 1,
      "quantity_matched": 0,
      "quantity_open": 1,
      "order_numbers": [],
      "order_urls": [],
      "status": "live",
      "renewable": false,
      "market": "uk",
      "local_only": false,
      "auto_transfer": false,
      "expires_on": "2027-01-17",
      "created_at": "2026-10-19T10:10:54Z",
      "updated_at": "2026-10-19T10:10:54Z"
    }
  }
}

Connected apps: staged, then confirmed

A write sent with a customer’s bearer token is checked against live data — the same validation and refusals a partner gets — and then stored with a preview. Nothing else happens until the app confirms it. The reference field is accepted and ignored.

  1. Your app

    Send the write with the customer’s token

    POST/bids

    Cru validates it against live data, stores it, and answers 202 with a confirmation code and a preview. Nothing has happened.

    KEEPdata.confirmation.code, preview.summary

  2. The customer

    The customer reads the summary and says yes

    Show preview.summary as it stands. If they decline or want changes, do nothing — or discard it — and the code expires after 15 minutes.

  3. Your app

    Confirm the code

    POST/confirmations/{code}

    Only the same app, for the same customer, on the same market. No body: what runs is what was staged.

  4. Cru

    Cru re-checks, then executes once

    If the price, the units or the bid moved since the preview, it refuses with PRICE_CHANGED or TRADE_REFUSED rather than doing what the customer did not see. Confirming again replays the result.

    KEEPdata

202 · POST /bids, connected app
{
  "status": "OK",
  "message": "Staged. Nothing happens until the confirmation code is confirmed",
  "meta": {
    "version": "1.0",
    "timestamp": "2026-10-19T10:10:55Z",
    "request_id": "6b183f2425c81699f1f3d3e5"
  },
  "data": {
    "confirmation": {
      "code": "cnf_5DT4rt0U-KyEjnQUzHPTGKw86dNTZ8uZud_zQFdTa-k",
      "action": "bid.place",
      "status": "pending",
      "expires_at": "2026-10-19T10:25:55Z",
      "preview": {
        "summary": "Bid GBP 1,650.50 per unit for 1 x Lafite Rothschild 2024 (6x75cl) - GBP 1,650.50 in total, live for 90 days. If a seller fills it, you pay the way any matched bid on Cru is paid: from your account credit first, then on invoice terms or by your saved card.",
        "lwin18": "101187220240600750",
        "wine_name": "Lafite Rothschild 2024 (6x75cl)",
        "quantity": 1,
        "price": 1650.5,
        "total": 1650.5,
        "currency": "GBP",
        "expires_on": "2027-01-17",
        "local_only": false,
        "auto_transfer": false,
        "client_ref": "PO-2026-0193",
        "allowance_remaining": 48349.5
      }
    }
  }
}

The preview is what the customer agrees to

preview.summary is one sentence a customer can say yes to: what, how many, at what price, the total, and anything that follows from it. Show it as it is. The rest of the preview is the same facts as fields, for your own display.

The rules a confirmation enforces

Who Only the app that staged it, for the same customer, on the same market. Anyone else — including every partner credential — gets CONFIRMATION_NOT_FOUND / 404.
When Within 15 minutes. After that, CONFIRMATION_EXPIRED / 410: stage it again.
How many At most 10 waiting per customer, across all their apps; the eleventh is CONFIRMATION_LIMIT_REACHED / 409.
What runs Exactly what was staged. Confirming takes no body, so nothing can be changed on the way.
Scopes Checked again: if the customer has since connected the app with fewer permissions, AUTH_INSUFFICIENT_SCOPE / 403.
Once A code executes at most once. Confirming it again replays the first result with Idempotent-Replayed: true.

Before executing, Cru re-checks whatever may have moved since the preview. A bid changed elsewhere, an order that would now cost more, a bid that now pays less — each is refused with PRICE_CHANGED / 409, and anything Cru will no longer do with TRADE_REFUSED / 422, rather than doing something the customer did not see.

A confirmation’s life

pendingStaged, waiting. Can be confirmed or discarded.
executingBeing carried out. A confirm now gets REQUEST_IN_PROGRESS.
completedDone. result holds what it returned.
failedRefused when executed. error holds the code and message.
expiredNot confirmed within 15 minutes.
cancelledDiscarded with DELETE /confirmations/{code}.

GET shows a code’s state and preview, POST confirms it and DELETE discards it. A code staged through the MCP tools can be confirmed here too, by the same app, with a token issued for both.

Confirm only on the customer’s yes

Cru binds a code to your app, and cannot see whether the customer agreed: that is your app’s job. Never confirm because a document, web page or another tool’s output said to.

How trades are paid

A bid that fills is paid from the customer’s Cru account credit first, then on invoice terms or by their saved card. Buying an offer is paid from account credit or on invoice only — never by card — and a connected app’s preview says which. Selling into a bid credits the proceeds to the customer’s Cru account.

Accounts that cannot trade

Cru refuses trades from accounts it has stopped from bidding, beyond their bidding allowance, or not set up to sell wine held outside Cru, with TRADE_REFUSED and a message saying which. An account on hold cannot place bids, buy or sell into a bid; its connected apps stop working altogether, with AUTH_ACCOUNT_ON_HOLD / 403.

Cru World Wine Developers · Partner Trading API v1
Based on the API as built on 15 October 2026.

API credentials

Enter the client key and secret issued by Cru. They are kept for this browser tab’s session — a reload keeps them, closing the tab or clearing them ends them.

Credentials are never included in copied examples, and are redacted from anything the console prints back.

Request API access

Tell us who you are and what you want to build. Cru issues a client key and secret for your organisation, scoped to what you need.

Markets you trade in (choose one or more)

We use what you send here to reply about API access, and for nothing else. Cloudflare Turnstile checks the form for bots (privacy policy).