SDKs & code examples
Cru publishes SDKs for PHP, Go and Python, and a Postman collection, all generated from one OpenAPI description of the v1 API. Every endpoint is a typed method and every answer a typed model. Each SDK also:
- keeps working when Cru adds a field, or a value to a field;
- retries a read, or a partner credential’s write, after a timeout or a passing error, sending the same
reference— made for you if you give none — so a write never acts twice; - connects with a partner credential’s key and secret, or a connected app’s access token, which it refreshes once when it expires;
- gives a connected app’s write back staged, with the preview to show the customer, and confirms it on their yes;
- pages through
limitandoffsetfor you.
The three are open source, under the Apache 2.0 licence. Without one, every endpoint page has plain PHP and Python beside it. Calling the API without an SDK →
Set your environment variables
export CRU_BASE_URL='https://us.cruworldwine.com/api/v1' export CRU_CLIENT_KEY='YOUR_CLIENT_KEY' export CRU_CLIENT_SECRET='YOUR_CLIENT_SECRET'
Use the API hostname for your environment and market. Keep the credentials in server environment variables or your application’s secret manager. A connected app uses CRU_ACCESS_TOKEN instead of the key and secret. Each SDK reads these when given nothing, and CRU_MARKET too: the market to call when CRU_BASE_URL is not set.
PHP SDK
PHP 8.1 or later, and Guzzle 7.5 or later.
composer require cruworldwine/cru-api-php
use CruWorldWine\Client; $cru = new Client(clientKey: 'cru_live_...', clientSecret: $secret, market: 'uk'); $client = $cru->ping()->client; foreach ($cru->market->listBids(['lwin' => ['123456720150600750'], 'limit' => 50])->autoPagingIterator() as $wine) { foreach ($wine->bids ?? [] as $bid) { echo $wine->wine_name, ': ', $bid->quantity, ' at ', $bid->price, ' ', $wine->currency, "\n"; } } $write = $cru->bids->place(['lwin18' => '123456720150600750', 'quantity' => 6, 'price' => 1200.50]);
$app = new Client(accessToken: $tokens['access_token']);
Go SDK
Go 1.23 or later, and nothing beyond the standard library.
go get github.com/cruworldwine/cru-api-go/cru@main This installs from GitHub until the first release. After it, go get github.com/cruworldwine/cru-api-go/cru does the same.
import "github.com/cruworldwine/cru-api-go/cru" client, err := cru.NewClient(cru.WithClientKey("cru_live_...", secret), cru.WithMarket("uk")) if err != nil { log.Fatal(err) } p, err := client.Ping(ctx) fmt.Println(p.Client.Market, p.Client.Currency, p.Client.Scopes)
client, err := cru.NewClient( cru.WithAccessToken(accessToken), cru.WithOAuthRefresh(refreshToken, clientID, clientSecret, func(t cru.Token) { save(t.AccessToken, t.RefreshToken) }), )
Python SDK
Python 3.9 or later, with Cru for plain code and AsyncCru for asyncio.
pip install git+https://github.com/cruworldwine/cru-api-python This installs from GitHub until the first release. After it, pip install cruworldwine does the same.
from cruworldwine import Cru with Cru(client_key='cru_live_...', client_secret='...', market='uk') as cru: client = cru.ping().client print(client.organisation, client.market, client.currency, client.scopes) for wine in cru.portfolio.list(is_listed=True).auto_paging_iter(): for line in wine.portfolio: print(wine.wine_name, line.quantity, line.pricing.listing_price, line.pricing.net_proceeds)
cru = Cru( access_token=tokens['access_token'], refresh_token=tokens['refresh_token'], oauth_client_id='your-client-id', oauth_client_secret=None, # only for a confidential client on_token_refresh=save_tokens, )
Postman collection
- Download the file in your browser. Postman’s Import from a link cannot fetch it from this site.
- In Postman, choose Import and drop the file in.
- In the collection’s variables, set
clientKeyandclientSecret, or a connected app’saccessToken, and themarketto call. - Run a folder top to bottom: each list saves the codes and ids the requests after it use, and a staged write saves its confirmation code.
A partner credential’s writes run at once, so running a folder of them places, changes and cancels a real bid, or lists real wine. Read freely, and send a write only when you mean it.
To import by link instead, into Postman or an OpenAPI code generator, use the OpenAPI description the SDKs are generated from: openapi.yaml .
Without an SDK
Every endpoint page has a PHP and a Python example beside it, built from the request on screen: plain PHP with the cURL extension, and Python with requests. Neither needs an SDK, and both follow the same v1 contract.
Make your first request
<?php $curl = curl_init(getenv('CRU_BASE_URL') . '/ping'); curl_setopt_array($curl, [ CURLOPT_HTTPHEADER => [ 'CLIENT-KEY: ' . getenv('CRU_CLIENT_KEY'), 'CLIENT-SECRET: ' . getenv('CRU_CLIENT_SECRET'), 'Accept: application/json', ], CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_TIMEOUT => 30, ]); $payload = json_decode(curl_exec($curl), true); print_r($payload['data']['client']);
import os import requests response = requests.get( os.environ["CRU_BASE_URL"] + "/ping", headers={ "CLIENT-KEY": os.environ["CRU_CLIENT_KEY"], "CLIENT-SECRET": os.environ["CRU_CLIENT_SECRET"], "Accept": "application/json", }, timeout=30, ) print(response.json()["data"]["client"])
Read every page
Advance offset by limit until meta.pagination.has_more is false. On the books a page is wines, each with its bids or offers.
import os import requests session = requests.Session() session.headers.update({ "CLIENT-KEY": os.environ["CRU_CLIENT_KEY"], "CLIENT-SECRET": os.environ["CRU_CLIENT_SECRET"], "Accept": "application/json", }) offset = 0 while True: page = session.get( os.environ["CRU_BASE_URL"] + "/market/bids", params={"limit": 250, "offset": offset}, timeout=30, ).json() for market in page["data"]["markets"]: print(market["lwin18"], market["wine_name"], len(market["bids"])) pagination = page["meta"]["pagination"] if not pagination["has_more"]: break offset += pagination["limit"]
Retry a write safely
Generate the reference once, store it, and send the same request again if the first gets no answer. Cru replays the first outcome rather than acting twice.
import os import uuid import requests # Generate once, and store it with your own record of the order. reference = "bid-" + uuid.uuid4().hex[:24] def place_bid(): return requests.post( os.environ["CRU_BASE_URL"] + "/bids", headers={ "CLIENT-KEY": os.environ["CRU_CLIENT_KEY"], "CLIENT-SECRET": os.environ["CRU_CLIENT_SECRET"], "Accept": "application/json", }, json={ "reference": reference, "lwin18": "101187220240600750", "quantity": 1, "price": 1650.50, }, timeout=30, ) try: response = place_bid() except requests.Timeout: # The same reference: placed at most once, however many times this runs. response = place_bid() print(response.status_code, response.headers.get("Idempotent-Replayed"))
Handle API errors
$payload = json_decode($body, true); if (($payload['status'] ?? null) !== 'OK') { $error = $payload['errors'][0] ?? []; // Log metadata only. Never log your secret. error_log(sprintf('%d %s %s %s', curl_getinfo($curl, CURLINFO_RESPONSE_CODE), $error['code'] ?? 'UNKNOWN', $error['field'] ?? '-', $payload['meta']['request_id'] ?? '-')); }
Prefer Python?
Install requests, set your credentials, then choose Python in an endpoint’s request panel.
python -m pip install requests