{
  "object": "doc",
  "title": "How do I call Cartroute from TypeScript?",
  "description": "A typed fetch client with retries and error handling.",
  "section": "Agents and integrations",
  "url": "https://cart-route.com/docs/guides/typescript",
  "updated": "2026-09-23",
  "prev": "https://cart-route.com/docs/guides/python",
  "next": "https://cart-route.com/docs/guides/no-code",
  "text": "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.\n\n## What does a small typed client look like?\n\n```\ntype Offer = {\n  offer_id: string;\n  retailer: { id: string; name: string; membership_required: boolean };\n  price_usd: number;\n  shipping_usd: number;\n  effective_price_usd: number;\n  availability: { in_stock: boolean; stock_level: string };\n  buy_url: string;\n};\ntype Product = {\n  id: string;\n  title: string;\n  price_summary: { best_retailer: string | null; best_effective_price_usd: number | null };\n  price_insight: { verdict: string; reason: string };\n  offers: Offer[];\n};\ntype SearchResult = { results: Product[]; next_cursor: string | null; credits: { charged: number; remaining: number } };\n\nexport class CartrouteError extends Error {\n  constructor(public status: number, public code: string, public problem: Record<string, unknown>) {\n    super(String(problem.detail ?? `HTTP ${status}`));\n  }\n}\n\nconst BASE = \"https://cart-route.com/api/v1\";\n\nasync function get<T>(path: string, params: Record<string, string> = {}, retries = 3): Promise<T> {\n  const url = new URL(BASE + path);\n  Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));\n  const idempotencyKey = crypto.randomUUID();\n  for (let attempt = 0; ; attempt++) {\n    let res: Response;\n    try {\n      res = await fetch(url, {\n        headers: { Authorization: `Bearer ${process.env.CARTROUTE_API_KEY}`, \"Idempotency-Key\": idempotencyKey },\n        signal: AbortSignal.timeout(15_000),\n      });\n    } catch (err) {\n      if (attempt >= retries) throw err;\n      await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));\n      continue;\n    }\n    if ((res.status === 429 || res.status >= 500) && attempt < retries) {\n      const wait = res.status === 429 ? Number(res.headers.get(\"Retry-After\") ?? 1) : 2 ** attempt;\n      await new Promise((r) => setTimeout(r, wait * 1000));\n      continue;\n    }\n    const body = await res.json();\n    if (!res.ok) throw new CartrouteError(res.status, body.code, body);\n    return body as T;\n  }\n}\n\nexport const cartroute = {\n  search: (q: string, filters: Record<string, string> = {}) => get<SearchResult>(\"/search\", { q, ...filters }),\n  product: (id: string) => get<{ product: Product }>(`/products/${encodeURIComponent(id)}`),\n  account: () => get<{ credits_remaining: number }>(\"/account\"),\n};\n```\n\n## How do I use it?\n\n```\nconst { results } = await cartroute.search(\"espresso machine\", { max_price: \"700\", in_stock: \"true\" });\nconst cheapest = results[0]?.offers[0];\nif (cheapest) {\n  console.log(`${cheapest.retailer.name}: $${cheapest.effective_price_usd.toFixed(2)} → ${cheapest.buy_url}`);\n}\n```\n\n## Can I generate the types instead?\n\nYes. 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."
}