Kopimore Developer API

A read-only REST API for retrieving your identified website visitors programmatically. JSON over HTTPS, API-key authentication, cursor pagination. Integration overview →

Basics

Base URL
https://www.kopimore.com/api/v1

All requests must use HTTPS and the www subdomain — requests to the bare domain are redirected with a 308, and most HTTP clients drop the Authorization header when following cross-host redirects.

The API is read-only: every endpoint is a GET. All responses are JSON. An active Kopimore subscription is required.

Authentication

Authenticate with an API key in the Authorization header. Create and revoke keys on the API Keys page in your portal. Keys are shown once at creation and stored only as a hash — if you lose one, revoke it and create a new one.

Request header
Authorization: Bearer kp_live_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b

Keys inherit your account's data access: all pixels, read-only. Never expose a key in client-side code, public repositories, or URLs. Requests without a valid key return 401; accounts without an active subscription return 403.

Rate Limits

Each key may make 60 requests per minute (fixed one-minute window). Every response includes rate limit headers:

Response headers
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 42

Exceeding the limit returns 429 with a Retry-After: 60 header. Back off until the next minute window. Need a higher limit? Email admin@kopimore.com.

Pagination

List endpoints use cursor pagination ordered by identified_at descending (newest first). Each page returns has_more and next_cursor; pass the cursor as ?before= on the next request.

Fetching page 2
# Page 1 GET /api/v1/leads?limit=100 # → { ..., "has_more": true, "next_cursor": "2026-07-10T12:45:00+00:00" } # Page 2 GET /api/v1/leads?limit=100&before=2026-07-10T12%3A45%3A00%2B00%3A00

To poll for new leads instead, store the newest identified_at you've seen and pass it as ?since= on your next poll.

Errors

Errors return an appropriate HTTP status and a consistent JSON body:

Error response
{ "error": { "code": "rate_limit_exceeded", "message": "Rate limit of 60 requests per minute exceeded. Retry shortly.", "docs": "https://kopimore.com/developers" } }
StatusCodeMeaning
401missing_api_keyNo Authorization: Bearer kp_live_... header sent.
401invalid_api_keyThe key does not exist.
401revoked_api_keyThe key was revoked in the portal.
403subscription_requiredThe account has no active subscription.
400invalid_parameter / invalid_cursorA query parameter is malformed (e.g. a non-ISO timestamp).
404unknown_pixelpixel_id doesn't belong to this account.
404not_foundUnknown endpoint path.
405method_not_allowedOnly GET is supported.
429rate_limit_exceededPer-minute rate limit hit. Honor Retry-After.
500internal_error / query_failedSomething failed on our side — retry, or contact support.

Endpoints

GET/leads

Returns identified visitors across your pixels, newest first.

Query paramTypeDescription
limitintegerResults per page, 1–100. Default 25.
beforeISO 8601Pagination cursor — only leads identified strictly before this time.
sinceISO 8601Only leads identified at or after this time.
pixel_idstringRestrict to one pixel. Must belong to your account (see GET /pixels).
statestringTwo-letter US state code, e.g. TX.
citystringCity name, case-insensitive exact match.
has_emailbooleantrue → only leads with an email address.
Example response
{ "object": "list", "data": [ { "id": "08ec1079-1060-4ec8-81c3-7d72114a86f2", "identified_at": "2026-07-10T12:45:00+00:00", "pixel_id": "your-pixel-id", "name": "Jane Sample", "email": "jane@example.com", "verified_email": "jane@example.com", "phone": "5551234567", "company": "Example Co", "city": "Austin", "state": "TX", "gender": "Female", "age_range": "45-54", "income_range": "$100K - $149K", "net_worth": "$250K - $499K", "marital_status": "Married", "homeowner": "Yes", "children": "Yes", "page_url": "https://yoursite.com/pricing", "referrer": "https://www.google.com/", "traffic_source": "organic" } ], "has_more": true, "next_cursor": "2026-07-10T12:45:00+00:00" }
FieldTypeDescription
iduuidUnique lead event ID.
identified_atISO 8601When the visitor was identified. Sort/cursor field.
pixel_idstringPixel that captured the visit.
namestring?Full name.
email / verified_emailstring?Best-known and personally-verified email.
phonestring?Phone number (digits only).
companystring?Employer / company name when resolved.
city / statestring?Location (US state code).
gender, age_range, income_range, net_worth, marital_status, homeowner, childrenstring?Household demographics. Ranges are bucketed strings.
page_url, referrer, traffic_sourcestring?Session context for the identifying visit.

Fields marked string? are null when not resolved for that visitor.

GET/pixels

Lists the pixels on your account. Use the returned IDs with the pixel_id filter on /leads.

Example response
{ "object": "list", "data": [ { "id": "your-pixel-id", "status": "active" } ] }

GET/usage

Request counts for the presented key. Note: calls to /usage itself count toward the totals and the rate limit.

Example response
{ "object": "usage", "requests_today": 118, "requests_this_month": 4302, "rate_limit_per_min": 60 }

Code Examples

curl — 25 newest leads with an email
curl "https://www.kopimore.com/api/v1/leads?has_email=true" \ -H "Authorization: Bearer $KOPIMORE_API_KEY"
JavaScript (Node 18+ / browserless) — page through all leads
const BASE = 'https://www.kopimore.com/api/v1'; const headers = { Authorization: `Bearer ${process.env.KOPIMORE_API_KEY}` }; async function allLeads() { const leads = []; let cursor = null; do { const url = new URL(`${BASE}/leads`); url.searchParams.set('limit', '100'); if (cursor) url.searchParams.set('before', cursor); const res = await fetch(url, { headers }); if (res.status === 429) { await new Promise(r => setTimeout(r, 60_000)); continue; } const page = await res.json(); leads.push(...page.data); cursor = page.has_more ? page.next_cursor : null; } while (cursor); return leads; }
Python — poll for new leads every 5 minutes
import os, time, requests BASE = "https://www.kopimore.com/api/v1" HEADERS = {"Authorization": f"Bearer {os.environ['KOPIMORE_API_KEY']}"} last_seen = None while True: params = {"limit": 100} if last_seen: params["since"] = last_seen r = requests.get(f"{BASE}/leads", headers=HEADERS, params=params) r.raise_for_status() page = r.json() for lead in page["data"]: print(lead["identified_at"], lead["name"], lead["email"]) if page["data"]: last_seen = page["data"][0]["identified_at"] time.sleep(300)

Questions or a use case we don't cover yet? Email admin@kopimore.com — we ship fast.

Ready to build?

Grab an API key from your portal and make your first request in under a minute.

Get Your API Key Integration Overview