KanalKanal API
Recipes

Node.js integration

A small, retry-safe client using fetch.

No SDK is required — the API is plain JSON over HTTPS. Here is a minimal, production-shaped client with retry handling.

kanal.ts
const BASE = 'https://api.getkanal.com/api';

type KanalOptions = { apiKey: string; storeId: number | string };

export class KanalClient {
  constructor(private opts: KanalOptions) {}

  private async request<T>(
    method: string,
    path: string,
    body?: unknown,
    attempt = 0,
  ): Promise<T> {
    const res = await fetch(`${BASE}/v1/stores/${this.opts.storeId}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${this.opts.apiKey}`,
        'Content-Type': 'application/json',
      },
      body: body ? JSON.stringify(body) : undefined,
    });

    // Retry 429 and 5xx — safe because writes are idempotent.
    if ((res.status === 429 || res.status >= 500) && attempt < 4) {
      const retryAfter = Number(res.headers.get('Retry-After')) || 0;
      const backoff = retryAfter * 1000 || 2 ** attempt * 500;
      await new Promise((r) => setTimeout(r, backoff));
      return this.request<T>(method, path, body, attempt + 1);
    }

    if (!res.ok) {
      const err = await res.json().catch(() => ({}));
      throw new Error(
        `Kanal ${res.status}: ${err.message ?? err.error ?? res.statusText}`,
      );
    }

    return res.status === 204 ? (undefined as T) : await res.json();
  }

  upsertOrder(order: Record<string, unknown>) {
    return this.request('POST', '/orders', order);
  }

  updateOrder(externalId: string, patch: Record<string, unknown>) {
    return this.request('PATCH', `/orders/${encodeURIComponent(externalId)}`, patch);
  }

  upsertCheckout(checkout: Record<string, unknown>) {
    return this.request('POST', '/checkouts', checkout);
  }

  upsertCustomer(customer: Record<string, unknown>) {
    return this.request('POST', '/customers', customer);
  }

  upsertShipment(shipment: Record<string, unknown>) {
    return this.request('POST', '/shipments', shipment);
  }
}
usage.ts
const kanal = new KanalClient({
  apiKey: process.env.KANAL_API_KEY!,
  storeId: 123,
});

await kanal.upsertOrder({
  external_id: 'ORDER-1001',
  placed_at: new Date().toISOString(),
  total_price: 49.9,
  currency: 'EUR',
  customer: { phone: '+33612345678', first_name: 'Marie' },
});

Read KANAL_API_KEY from the environment. Never hardcode keys or expose them to the browser.