Quickstart
Go from no account to a delivered test lead in five minutes. Every step uses the live API. Copy, paste, run.
Before you start
You need:
- A LeadRails workspace. Sign up free if you don't have one.
- A terminal with
curl, OR Node 22+ for the TypeScript examples. - One destination ready to receive a test event. The fastest is a Slack incoming webhook URL — grab one from Slack's webhook setup if you don't already have one.
Through this guide, replace $LR_KEY with your real API key
(created in step 1). Replace placeholder IDs like
cli_01J... and src_01J... with the ones the
API returns to you.
1. Create an API key
API keys are workspace-scoped. They authorize every call to the v1 surface. Create one from the dashboard:
- Open app.leadrails.dev/settings/api-keys.
- Click Create key. Name it something memorable (e.g. "local-dev").
- Copy the full key — it starts with
lr_live_and is shown once. You will not be able to retrieve the plaintext again.
Export it into your shell:
export LR_KEY="lr_live_yourkeyhere"
Verify the key works by calling GET /v1/me — the cheapest
round-trip on the API. It returns your workspace identity:
curl -s https://api.leadrails.dev/v1/me \
-H "Authorization: Bearer $LR_KEY" | jq Expected response:
{
"client_id": "cli_01J5Z7K3M8X4VWPQ9YTBN2F0HR",
"client_name": "Acme Plumbing",
"plan": "starter"
}
A 401 means the key is wrong or revoked.
See Errors for the full list.
2. Create a source
A source is where leads come from — typically your website's contact form. Each source has a signing secret that the upstream form-handler uses to authenticate inbound posts.
Every POST and PATCH on the v1 surface
requires an Idempotency-Key header.
Generate a fresh value (a UUID is fine) per logical operation. If you
retry the same request with the same key + body, the API returns the
original result instead of creating a duplicate.
Curl:
curl -s -X POST https://api.leadrails.dev/v1/sources \
-H "Authorization: Bearer $LR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Website contact form",
"source_system": "wordpress"
}' | jq
The response includes the source_id AND the
plaintext signing_secret. The plaintext secret
is shown once. Capture it — the upstream form-handler will
need it to sign inbound HMAC requests.
{
"source_id": "src_01J5Z7N9X3M2VWPQ9YTBN2F0HR",
"name": "Website contact form",
"source_system": "wordpress",
"status": "active",
"signing_secret": "sk_src_..."
} TypeScript (openapi-fetch):
import createClient from "openapi-fetch";
import type { paths } from "./lr-openapi-types"; // see the reference page
const lr = createClient<paths>({
baseUrl: "https://api.leadrails.dev",
headers: { Authorization: `Bearer ${process.env.LR_KEY}` },
});
const { data, error } = await lr.POST("/v1/sources", {
headers: { "Idempotency-Key": crypto.randomUUID() },
body: {
name: "Website contact form",
source_system: "wordpress",
},
});
if (error) throw new Error(error.title);
const sourceId = data.source_id; 3. Create a destination
A destination is where a lead lands. The adapter type controls how the payload gets shaped. We'll use a Slack webhook here because it's the fastest to verify visually.
curl -s -X POST https://api.leadrails.dev/v1/destinations \
-H "Authorization: Bearer $LR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"name": "Team Slack — new leads",
"adapter_type": "slack_webhook",
"config": {
"webhook_url": "https://hooks.slack.com/services/T000/B000/XXX"
}
}' | jq Response:
{
"destination_id": "dst_01J5Z7Q1A2M2VWPQ9YTBN2F0HR",
"name": "Team Slack — new leads",
"adapter_type": "slack_webhook",
"status": "active"
}
If the webhook URL fails the safe-outbound-URL check (private network,
unsupported scheme, etc.) the API returns a 422 unsafe-url problem. Fix the URL and retry.
4. Wire a route
A route connects a source to a destination. One source can fan out to many destinations by creating multiple routes against the same source.
curl -s -X POST https://api.leadrails.dev/v1/routes \
-H "Authorization: Bearer $LR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"source_id": "src_01J5Z7N9X3M2VWPQ9YTBN2F0HR",
"destination_id": "dst_01J5Z7Q1A2M2VWPQ9YTBN2F0HR",
"name": "Form → Slack"
}' | jq
Cross-workspace IDs are rejected with 400 invalid-reference. The source_id and destination_id must both live in the workspace the API key belongs to.
5. Fire a test event
Test events go through the intake surface, not the admin REST API. The intake surface requires HMAC signing — the dashboard ships an in-browser test-event sender that does the signing for you. Easiest path:
- Open your sources list in the dashboard.
- Click your new source.
- Click Send test event.
Within seconds, your Slack channel should show a message and the admin API will have a new event row.
Full server-to-server HMAC signing is documented under HMAC signing guide — it's the right path once you're integrating from your own backend.
6. Confirm delivery
Poll GET /v1/events to see what happened. The list is
sorted newest-first; the status tells you whether each
destination delivery succeeded, is retrying, or failed.
curl -s "https://api.leadrails.dev/v1/events?limit=5" \
-H "Authorization: Bearer $LR_KEY" | jq Response:
{
"data": [
{
"event_id": "evt_01J5Z7S5K3M2VWPQ9YTBN2F0HR",
"source_id": "src_01J5Z7N9X3M2VWPQ9YTBN2F0HR",
"received_at": "2026-05-23T14:02:11Z",
"deliveries": [
{
"destination_id": "dst_01J5Z7Q1A2M2VWPQ9YTBN2F0HR",
"status": "delivered",
"delivered_at": "2026-05-23T14:02:12Z",
"attempts": 1
}
]
}
],
"next_cursor": null
} /v1/events requires a Pro+ plan. On Starter the call returns 403 plan-required; the same data lives in the dashboard under
Events on every plan.
What's next
- Browse the full API reference — every endpoint, every field.
- Install the MCP server — talk to LeadRails from Claude Desktop or Cursor.
- Read the errors page — every
typeURL the API emits, with the fix. - HMAC signing guide — full intake contract for server-to-server posting.
Stuck? Email us.
Include the request_id header from any failing response — we use
it to find your request in the logs.