Call the API in 5 minutes

Create an API key, check who you are, and list and create leads with curl, Python or TypeScript.

Create an API key

In the ZeroReach app, open Settings → API keys and create a key with the scopes you need. For this quickstart: leads:read and leads:write (or leads:*, which grants both).

The key looks like ro_pat_ followed by 43 characters. It is shown once: only a hash is stored, so copy it now.

Treat the key like a password. It acts as you, in one workspace, and is capped by your current role. Never commit it; keep it in an environment variable.

Keys are created by a signed-in person. An API key can't create or revoke keys (POST /api/v1/workspaces/current/api-keys refuses API keys).

Set two environment variables

export ZEROREACH_URL="http://localhost:8000"   # or your ZeroReach host
export ZEROREACH_API_KEY="ro_pat_..."           # the key you just created

Check who you are

GET /api/v1/me works with any valid key, whatever its scopes. The key implies its workspace, so you don't need an X-Workspace-Id header.

curl -s "$ZEROREACH_URL/api/v1/me" \
  -H "Authorization: Bearer $ZEROREACH_API_KEY"
import os

import httpx

client = httpx.Client(
    base_url=os.environ["ZEROREACH_URL"],
    headers={"Authorization": f"Bearer {os.environ['ZEROREACH_API_KEY']}"},
)

me = client.get("/api/v1/me")
me.raise_for_status()
print(me.json()["user"])
const base = process.env.ZEROREACH_URL!;
const headers = { Authorization: `Bearer ${process.env.ZEROREACH_API_KEY}` };

const res = await fetch(`${base}/api/v1/me`, { headers });
if (!res.ok) throw new Error(`${res.status}: ${JSON.stringify(await res.json())}`);
const me = await res.json();
console.log(me.user);

List leads

Lists are newest first and paged with a cursor: pass next_cursor back as cursor until it is null.

curl -s "$ZEROREACH_URL/api/v1/leads?limit=25" \
  -H "Authorization: Bearer $ZEROREACH_API_KEY"
cursor = None
while True:
    params = {"limit": 100, **({"cursor": cursor} if cursor else {})}
    page = client.get("/api/v1/leads", params=params).json()
    for lead in page["items"]:
        print(lead["id"], lead.get("email"))
    cursor = page["next_cursor"]
    if cursor is None:
        break
let cursor: string | null = null;
do {
  const url = new URL(`${base}/api/v1/leads`);
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const page = await (await fetch(url, { headers })).json();
  for (const lead of page.items) console.log(lead.id, lead.email);
  cursor = page.next_cursor;
} while (cursor);

Create a lead

Leads are deduplicated on email within a workspace: creating a second live lead with the same email answers 409 with the existing lead's id (existing_id).

curl -s -X POST "$ZEROREACH_URL/api/v1/leads" \
  -H "Authorization: Bearer $ZEROREACH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"first_name": "Ana", "last_name": "Silva", "email": "ana@example.com", "timezone": "America/New_York"}'
lead = client.post(
    "/api/v1/leads",
    json={"first_name": "Ana", "last_name": "Silva", "email": "ana@example.com"},
)
if lead.status_code == 409:
    print("Already exists:", lead.json()["detail"])
else:
    lead.raise_for_status()
    print(lead.json()["id"])
const created = await fetch(`${base}/api/v1/leads`, {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ first_name: "Ana", last_name: "Silva", email: "ana@example.com" }),
});
console.log(created.status, await created.json());

What can go wrong

StatusMeaning
401Invalid or expired API key. The key is wrong, revoked or expired.
403This API key lacks the '<scope>' scope., or a route API keys can't use.
409A conflict, e.g. a duplicate lead.
422The request didn't validate; detail lists each problem.
429Too many requests for this key (default 600 per minute) or too many failed key attempts from your IP. Wait for Retry-After seconds.

Next: Authentication, API basics and the API reference.