Skip to content

JavaScript sample

A minimal Node.js client for the BottleCRM API. Uses native fetch (Node 18+), no external dependencies.

A tiny client

const BASE = process.env.BOTTLECRM_URL.replace(/\/$/, "");
const TOKEN = process.env.BOTTLECRM_TOKEN;

async function api(path, init = {}) {
  const res = await fetch(`${BASE}${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${TOKEN}`,
      "Content-Type": "application/json",
      ...(init.headers ?? {}),
    },
  });
  if (!res.ok) {
    const body = await res.text();
    throw new Error(`${res.status} ${res.statusText}: ${body}`);
  }
  return res.status === 204 ? null : res.json();
}

export async function* listLeads(filters = {}) {
  const qs = new URLSearchParams(filters).toString();
  let url = `/api/leads/${qs ? `?${qs}` : ""}`;
  while (url) {
    const page = await api(url);
    yield* page.results;
    url = page.next ? new URL(page.next).pathname + new URL(page.next).search : null;
  }
}

export const createLead = (fields) =>
  api("/api/leads/", { method: "POST", body: JSON.stringify(fields) });

export const updateLead = (id, fields) =>
  api(`/api/leads/${id}/`, { method: "PATCH", body: JSON.stringify(fields) });

Example: poll for changes

BottleCRM sends no outbound webhooks, so reacting to events means asking. Keep a high-water mark and ask for what has changed since you last looked.

import { listLeads } from "./bottlecrm-client.js";

let since = new Date(Date.now() - 60_000).toISOString();

async function poll() {
  const now = new Date().toISOString();
  for await (const lead of listLeads({ created_at__gte: since })) {
    await enqueueOnboardingEmail(lead.email);
  }
  // Advance only after the batch is handled, so a crash re-reads rather than skips.
  since = now;
}

setInterval(() => poll().catch(console.error), 60_000);

Example: build a custom dashboard widget

import { listLeads } from "./bottlecrm-client.js";

async function thisWeekByStatus() {
  const buckets = { new: 0, contacted: 0, qualified: 0, lost: 0, converted: 0 };
  const since = new Date(Date.now() - 7 * 24 * 3600 * 1000).toISOString();

  for await (const lead of listLeads({ created_at__gte: since })) {
    buckets[lead.status] = (buckets[lead.status] ?? 0) + 1;
  }
  return buckets;
}

console.log(await thisWeekByStatus());

Browser usage

In the browser, never embed a JWT in client code. Instead proxy through your own server, or use a session cookie that your origin sets after BottleCRM's OAuth flow.