Lead Capture API

Every site you own. One pipeline in.

A public, key-authenticated endpoint that turns any contact form, landing page, or checkout flow into a lead in your Rook Dots workspace. No OAuth dance, no SDK, one POST request.

How it works

Four steps. No waiting on us.

You control the whole thing from your workspace settings, no ticket, no approval queue.

1. Create a key

Open Lead Settings in your workspace, name the key after the site that will use it, generate it.

2. Send a request

POST the lead’s details to the public endpoint with your key in the X-Rook-Key header.

3. It lands in your board

The lead appears in your Leads pipeline instantly, tagged with which key captured it.

4. Annotate later

Once qualified further, add a follow-up note to that same lead through the same key.

Full transparency

Exactly how the security model works.

Not a summary. The actual mechanics, so you can decide for yourself whether it's safe enough for you.

We never store your key.

Only a SHA-256 hash of it lives in our database. The plaintext key is shown to you exactly once, at creation. If you lose it, you generate a new one. We cannot recover the old one, and neither could anyone who read our database.

CORS is wide open on purpose.

This endpoint allows any origin. The key is the access boundary, not the browser origin, since we can't maintain an allowlist of every customer's domain. If you want browser-side calls locked to specific origins, set an origin allowlist per key when you create it.

Two layers of rate limiting.

30 requests/minute per IP before your key is even checked, and 60 requests/minute per key after. Both are enforced in Redis, so they hold across every server instance, not just one.

Honeypot, not a puzzle.

Add a hidden field named hp to your form. Real visitors never fill it. Bots that auto-fill every input do, and get a fake success response instead of a lead being created, so they never learn they were caught.

Write-only, no read access.

A capture key can create a lead and add a note to a lead it created. It cannot list, read, or export anything else in your workspace, so leaking a key exposes nothing that isn't already going into it.

Custom fields

Budget, currency, source, services. Yours, not ours.

Every workspace configures its own dropdown values in Lead Settings, no two companies sell the same services or think in the same budget bands.

Configured, not hardcoded

Budget ranges, currencies (all ~150 ISO 4217 codes to choose from), lead sources, and your service catalog all live in Lead Settings inside the workspace. The Dots UI only ever shows these as dropdowns or tag pickers, never free typing, so the pipeline can't drift from what it's actually tracking.

The API is flexible on purpose

Nothing is required except a name. Send only the fields your form actually collects. Fetch the current dropdown values with the config endpoint below and map your own form's labels onto them, that's how you connect your service list to ours.

Implement it in minutes

Hand this to your AI coding tool.

Copy the prompt below into Claude Code, Cursor, Copilot, or anything else that can edit your codebase. It has everything needed to wire this up correctly and safely.

Ready-to-paste implementation prompt
Implement Rook Dots lead capture in this project.

Endpoint: POST https://dotsapi.rookhq.com/api/v1/public/leads
Auth header: X-Rook-Key: <my capture key, stored server-side only, e.g. process.env.ROOK_DOTS_KEY>

Do this:
1. Find my existing contact/signup form (or create one if none exists) and identify its fields.
2. Add a server-side route/endpoint in this codebase (do NOT call the Rook Dots API directly from client-side JS, the key must never reach the browser).
3. Optionally, GET https://dotsapi.rookhq.com/api/v1/public/leads/config (same X-Rook-Key header) first to fetch the workspace's configured budget ranges, currencies, sources, and services, then map my form's own fields onto that vocabulary where they overlap (e.g. if my form has a "services" checklist, match each of my labels to the closest one in config.services and send their "value", not my own label).
4. From that server route, POST JSON to the endpoint above with a body built from the form fields, mapped to this shape:
   {
     "name": string (required),
     "email": string (optional, valid email, strictly validated server-side),
     "phone": string (optional, pre-formatted), OR "phoneCountryCode" + "phoneNumber" (optional, sent separately, e.g. "+1" and "5550100"),
     "businessName": string (optional),
     "budgetRange": string (optional, ideally one of config.budgetRanges[].value if you fetched config, otherwise free text),
     "currency": string (optional, ideally one of config.currencies[].value),
     "servicesInterested": string[] (optional, ideally values from config.services[].value),
     "notes": string (optional, fold in any extra form fields that don't map to the above as readable text here)
   }
5. Add a hidden form field named "hp" (a honeypot) that must stay empty, include it in the POST body as "hp". Real users never fill it; if a bot does, the API silently accepts the request without creating a lead, so don't treat a 201 as proof a human submitted it.
6. Handle the response: 201 means the payload was accepted (data.id is the new lead's id, store it if you need to reference the lead later, e.g. to add a note). Handle 400 (validation, show field errors), 401/403 (misconfigured key, log it, don't show the user a raw error), and 429 (rate limited, retry with backoff or ask the user to try again shortly).
7. Read the key from an environment variable (e.g. ROOK_DOTS_KEY). Never hardcode it, never commit it, never send it in a client-side bundle.
8. Keep this best-effort: if the Rook Dots call fails, log it but don't block the user's form submission on it, unless Rook Dots is the only place this data is stored.
API reference

Everything the endpoint accepts.

POST/api/v1/public/leads

Creates a lead in the workspace the key belongs to. Requires the X-Rook-Key header.

Body fields

name
string

Required, up to 200 characters.

email
string

Optional, strictly validated, must be a real-looking address.

phone
string

Optional, pre-formatted, up to 32 characters.

phoneCountryCode
string

Optional alternative to phone, e.g. "+1". Pair with phoneNumber.

phoneNumber
string

Optional, 6-14 digits (loose on purpose, lengths vary by country).

businessName
string

Optional, up to 200 characters.

budgetRange
string

Optional, ideally a value from GET /config's budgetRanges.

currency
string

Optional, ideally a value from GET /config's currencies.

servicesInterested
string[]

Optional, up to 20 items, ideally from GET /config's services.

notes
string

Optional, up to 2000 characters.

hp
string

Optional honeypot. Must stay empty, see above.

Every lead created through this endpoint is tagged Source = "Website" automatically — it isn't a request field, and it can't be overridden or removed from your workspace's Source list. If you need to distinguish between multiple sites or forms sharing one key, put that detail in notes instead.

cURL
curl -X POST https://dotsapi.rookhq.com/api/v1/public/leads \
  -H "Content-Type: application/json" \
  -H "X-Rook-Key: rdk_live_your_key_here" \
  -d '{
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone": "+1 555 0100",
    "businessName": "Acme Co",
    "budgetRange": "$10k - $30k",
    "notes": "Interested in the Pro plan."
  }'
201 Response
{
  "data": { "id": "fb24cc97-dc9d-4447-9c99-5102c5f43d7d", "received": true },
  "message": "Lead received"
}
GET/api/v1/public/leads/config

Returns the workspace's current budget ranges, currencies, sources, and services, exactly what the Dots UI itself shows as dropdowns. Fetch this to render matching form fields, or to map your own service list onto the workspace's. Pipeline stages are not included, those are internal.

cURL
curl https://dotsapi.rookhq.com/api/v1/public/leads/config \
  -H "X-Rook-Key: rdk_live_your_key_here"
200 Response
{
  "data": {
    "budgetRanges": [{ "value": "10k - 25k", "label": "10k - 25k" }, "..."],
    "currencies": [{ "value": "USD", "label": "USD ($)" }, "..."],
    "sources": [{ "value": "Referral", "label": "Referral" }, "..."],
    "services": [{ "value": "brand_strategy", "label": "Brand strategy" }, "..."]
  }
}

Server-side (recommended)

Call this from your backend, not the browser. The key should never ship in client-side JavaScript.

Node / Next.js route handler
fetch('https://dotsapi.rookhq.com/api/v1/public/leads', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Rook-Key': process.env.ROOK_DOTS_KEY, // server-side only
  },
  body: JSON.stringify({
    name: 'Jane Doe',
    email: 'jane@example.com',
    phone: '+1 555 0100',
    // hp: leave this hidden field empty, it's a spam honeypot
  }),
});
POST/api/v1/public/leads/:id/notes

Adds a note to a lead this same key created earlier, useful once a raw form-fill turns into something qualified (a booked call, an approved proposal). A key can only annotate leads it captured itself; anything else returns a 404, whether or not the lead exists.

cURL
curl -X POST https://dotsapi.rookhq.com/api/v1/public/leads/{leadId}/notes \
  -H "Content-Type: application/json" \
  -H "X-Rook-Key: rdk_live_your_key_here" \
  -d '{ "note": "Discovery call booked for Thursday." }'

Errors

400VALIDATION_ERROR

A field failed validation. The response body lists which.

401UNAUTHORIZED

Missing X-Rook-Key header, or the key is invalid or inactive. Same message either way, on purpose.

403FORBIDDEN

The key has an origin allowlist set and the request’s Origin header isn’t on it.

404NOT_FOUND

On the notes endpoint: the lead doesn’t exist, or wasn’t created by this key.

429RATE_LIMIT_EXCEEDED

Too many requests. Check the RateLimit-* response headers for when to retry.

Common questions

Plain.

Where do I get a key?

+

Sign in to your workspace, open Lead Settings in the sidebar, go to the Lead Capture API tab, and create a key. The plaintext value is shown once, copy it immediately, we cannot show it again.

Can I use this directly from client-side JavaScript?

+

You can. CORS allows any origin, and a key with no origin allowlist works from anywhere. But because the key is a bearer credential, we recommend calling it from your server and keeping the key out of the browser bundle entirely.

What happens if my key leaks?

+

Revoke it from the Lead Capture page in your workspace and create a new one. A leaked key can only create leads and annotate the ones it created. It cannot read your workspace, so the blast radius is spam leads, not a data breach.

Does this count against my plan’s lead limits?

+

Leads created through the API count the same as any other lead in your workspace.

Can I update a lead’s status or other fields through the API?

+

Not through the public endpoint, only creation and notes. Status and other fields are meant to be managed by your team inside Rook Dots, not silently overwritten by an external system.

Wire it up today.

Free workspaces get lead capture keys too, no plan gate.