Sandbox environment: prices, stock and coupons are synthetic test data. What this means

How do I call Cartroute from TypeScript?

Use the built-in fetch (Node.js 18+, Deno, Bun, edge runtimes) with the key in an Authorization header, a timeout via AbortSignal.timeout, one Idempotency-Key per logical request and retries only for 429, 5xx and network errors. Types for the response come straight from the OpenAPI description.

What does a small typed client look like?

type Offer = {
  offer_id: string;
  retailer: { id: string; name: string; membership_required: boolean };
  price_usd: number;
  shipping_usd: number;
  effective_price_usd: number;
  availability: { in_stock: boolean; stock_level: string };
  buy_url: string;
};
type Product = {
  id: string;
  title: string;
  price_summary: { best_retailer: string | null; best_effective_price_usd: number | null };
  price_insight: { verdict: string; reason: string };
  offers: Offer[];
};
type SearchResult = { results: Product[]; next_cursor: string | null; credits: { charged: number; remaining: number } };

export class CartrouteError extends Error {
  constructor(public status: number, public code: string, public problem: Record<string, unknown>) {
    super(String(problem.detail ?? `HTTP ${status}`));
  }
}

const BASE = "https://cart-route.com/api/v1";

async function get<T>(path: string, params: Record<string, string> = {}, retries = 3): Promise<T> {
  const url = new URL(BASE + path);
  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
  const idempotencyKey = crypto.randomUUID();
  for (let attempt = 0; ; attempt++) {
    let res: Response;
    try {
      res = await fetch(url, {
        headers: { Authorization: `Bearer ${process.env.CARTROUTE_API_KEY}`, "Idempotency-Key": idempotencyKey },
        signal: AbortSignal.timeout(15_000),
      });
    } catch (err) {
      if (attempt >= retries) throw err;
      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
      continue;
    }
    if ((res.status === 429 || res.status >= 500) && attempt < retries) {
      const wait = res.status === 429 ? Number(res.headers.get("Retry-After") ?? 1) : 2 ** attempt;
      await new Promise((r) => setTimeout(r, wait * 1000));
      continue;
    }
    const body = await res.json();
    if (!res.ok) throw new CartrouteError(res.status, body.code, body);
    return body as T;
  }
}

export const cartroute = {
  search: (q: string, filters: Record<string, string> = {}) => get<SearchResult>("/search", { q, ...filters }),
  product: (id: string) => get<{ product: Product }>(`/products/${encodeURIComponent(id)}`),
  account: () => get<{ credits_remaining: number }>("/account"),
};

How do I use it?

const { results } = await cartroute.search("espresso machine", { max_price: "700", in_stock: "true" });
const cheapest = results[0]?.offers[0];
if (cheapest) {
  console.log(`${cheapest.retailer.name}: $${cheapest.effective_price_usd.toFixed(2)} → ${cheapest.buy_url}`);
}

Can I generate the types instead?

Yes. Point any OpenAPI 3.1 code generator at https://cart-route.com/openapi.json. The hand-written types above cover the fields most agents use.