How do I call Cartroute from Python?
Use requests (or httpx) with the key in an Authorization header, a timeout, an Idempotency-Key per logical request, and a retry loop that retries only rate limits, server errors and network failures. The client below does all of that in about 50 lines.
What does a small production client look like?
import os
import time
import uuid
import requests
class CartrouteError(Exception):
def __init__(self, status, problem):
super().__init__(problem.get("detail", f"HTTP {status}"))
self.status = status
self.code = problem.get("code")
self.problem = problem
class Cartroute:
def __init__(self, api_key=None, base_url="https://cart-route.com/api/v1", timeout=15, max_retries=3):
self.base_url = base_url.rstrip("/")
self.timeout = timeout
self.max_retries = max_retries
self.session = requests.Session()
self.session.headers["Authorization"] = f"Bearer {api_key or os.environ['CARTROUTE_API_KEY']}"
def _get(self, path, params=None):
key = str(uuid.uuid4()) # same key on every retry of this call
for attempt in range(self.max_retries + 1):
try:
resp = self.session.get(f"{self.base_url}{path}", params=params,
headers={"Idempotency-Key": key}, timeout=self.timeout)
except requests.RequestException:
if attempt == self.max_retries:
raise
time.sleep(2 ** attempt)
continue
if resp.status_code == 429 and attempt < self.max_retries:
time.sleep(int(resp.headers.get("Retry-After", "1")))
continue
if resp.status_code >= 500 and attempt < self.max_retries:
time.sleep(2 ** attempt)
continue
body = resp.json()
if not resp.ok:
raise CartrouteError(resp.status_code, body)
return body
def search(self, q, **filters):
return self._get("/search", {"q": q, **filters})
def product(self, product_id):
return self._get(f"/products/{product_id}")["product"]
def price_history(self, product_id, days=90):
return self._get(f"/products/{product_id}/price-history", {"days": days})["history"]
def coupons(self, **filters):
return self._get("/coupons", filters)["coupons"]
def account(self):
return self._get("/account")
How do I use it?
cr = Cartroute()
try:
result = cr.search("robot vacuum", max_price=600, in_stock="true", include_membership="false")
except CartrouteError as e:
if e.code == "insufficient_credits":
print("Out of credits:", e.problem.get("top_up_url"))
else:
raise
else:
for p in result["results"]:
best = p["price_summary"]
print(f'{p["title"]}: {best["best_retailer"]} ${best["best_effective_price_usd"]:.2f}',
"-", p["price_insight"]["verdict"])
print("credits left:", result["credits"]["remaining"])
Why these choices?
- One idempotency key per call, reused across retries, so a timeout followed by a retry is never charged twice.
- No retry on 4xx (except 429): the answer will not change.
- Booleans as strings in query parameters (
"true","false"), becauserequestswould send Python'sTrue.