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.
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.
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.
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.
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.
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.Everything the endpoint accepts.
/api/v1/public/leadsCreates a lead in the workspace the key belongs to. Requires the X-Rook-Key header.
Body fields
nameRequired, up to 200 characters.
emailOptional, strictly validated, must be a real-looking address.
phoneOptional, pre-formatted, up to 32 characters.
phoneCountryCodeOptional alternative to phone, e.g. "+1". Pair with phoneNumber.
phoneNumberOptional, 6-14 digits (loose on purpose, lengths vary by country).
businessNameOptional, up to 200 characters.
budgetRangeOptional, ideally a value from GET /config's budgetRanges.
currencyOptional, ideally a value from GET /config's currencies.
servicesInterestedOptional, up to 20 items, ideally from GET /config's services.
notesOptional, up to 2000 characters.
hpOptional 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 -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."
}'{
"data": { "id": "fb24cc97-dc9d-4447-9c99-5102c5f43d7d", "received": true },
"message": "Lead received"
}/api/v1/public/leads/configReturns 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 https://dotsapi.rookhq.com/api/v1/public/leads/config \
-H "X-Rook-Key: rdk_live_your_key_here"{
"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.
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
}),
});/api/v1/public/leads/:id/notesAdds 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 -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_ERRORA field failed validation. The response body lists which.
401UNAUTHORIZEDMissing X-Rook-Key header, or the key is invalid or inactive. Same message either way, on purpose.
403FORBIDDENThe key has an origin allowlist set and the request’s Origin header isn’t on it.
404NOT_FOUNDOn the notes endpoint: the lead doesn’t exist, or wasn’t created by this key.
429RATE_LIMIT_EXCEEDEDToo many requests. Check the RateLimit-* response headers for when to retry.
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.