Overview
The LeadGrid API lets you create and manage dossiers, update stages, attach notes, and sync data with your own tools. Every request returns JSON.
All timestamps are ISO 8601 in UTC. All amounts are integers or decimals in the smallest practical unit (e.g. euros, not cents). Pagination uses page and per_page query parameters; responses include a meta object with the total count.
Authentication
Every request must carry an API key in the Authorization header. Keys start with lg_live_ and are scoped to one organization.
- 1Go to Settings → API and click Create API key.
- 2Pick the scopes you need (see table below) and optionally set an expiry. The full key is shown once — copy it immediately.
- 3Send the key as a bearer token on every request.
curl https://leadgrid.io/api/v1/dossiers \ -H "Authorization: Bearer lg_live_your_key_here"
| Field | Type | Description |
|---|---|---|
| dossiers:read | scope | List and retrieve dossiers. |
| dossiers:write | scope | Create, update and archive dossiers. Also required to move stages. |
| flows:read | scope | List flows and read their stages. |
| notes:read | scope | Read notes on dossiers. |
| notes:write | scope | Add new notes to dossiers. |
Dossiers
Dossiers are the entities you track — candidates in recruitment or leads in sales. Each dossier belongs to exactly one flow and sits in exactly one stage.
/dossiersList dossiers in your organization. Supports filtering and pagination.
| Field | Type | Description |
|---|---|---|
| type | string | 'candidate' or 'sales'. |
| status | string | 'active', 'won', 'lost' or 'archived'. |
| stage_id | uuid | Return only dossiers currently in this stage. |
| page | integer | 1-based page number. Default: 1. |
| per_page | integer | Items per page (max 100). Default: 25. |
curl "https://leadgrid.io/api/v1/dossiers?type=sales&status=active" \ -H "Authorization: Bearer lg_live_your_key"
{
"data": [
{
"id": "6f2b…",
"type": "sales",
"name": "Rabobank — Talent rollout",
"company": "Rabobank",
"contact_person": "Mark de Vries",
"deal_size": 45000,
"deal_currency": "EUR",
"status": "active",
"current_stage_id": "c3e1…",
"assigned_to": "ab12…",
"created_at": "2026-04-10T09:21:14Z"
}
],
"meta": { "total": 34, "page": 1, "per_page": 25 }
}/dossiersCreate a new dossier. If flow_id is omitted the default flow for the given type is used; the dossier starts in that flow's first stage. Send application/json for a plain create, or multipart/form-data with a 'cv' file field to create the dossier AND attach a PDF CV in one atomic call — if the upload fails, the dossier is rolled back.
| Field | Type | Description |
|---|---|---|
| type* | string | 'candidate' or 'sales'. |
| name* | string | For candidates: the person's name. For sales: deal or account name. |
| string | Primary contact email. | |
| phone | string | Primary contact phone. |
| company | string | Candidate: current employer. Sales: target company. |
| role | string | Candidate: role they're applying for. Sales: role of the contact. |
| flow_id | uuid | Override the default flow. Must belong to your organization. |
| assigned_to | uuid | User ID of the member to assign this dossier to. |
| intake_notes | string | Director/intake notes shown in the dossier drawer. |
| contact_person | string | Sales only. Named contact at the target company. |
| deal_size | number | Sales only. Expected contract value. |
| deal_currency | string | Sales only. ISO 4217 currency code (e.g. 'EUR'). |
| cv | file (pdf) | multipart/form-data only. Optional PDF CV (max 10 MB). Uploaded and attached in the same call. If the upload fails, the dossier is rolled back. |
# JSON — plain create
curl -X POST https://leadgrid.io/api/v1/dossiers \
-H "Authorization: Bearer lg_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"type": "sales",
"name": "KLM — Cabin crew hiring",
"company": "KLM",
"contact_person": "Pieter van Leeuwen",
"deal_size": 62000,
"deal_currency": "EUR"
}'
# multipart — create + attach CV in one call
curl -X POST https://leadgrid.io/api/v1/dossiers \
-H "Authorization: Bearer lg_live_your_key" \
-F "type=candidate" \
-F "name=Sophie van Dijk" \
-F "role=Senior Frontend Engineer" \
-F "email=sophie@example.com" \
-F "cv=@./resume.pdf"{
"data": {
"id": "a91c…",
"type": "candidate",
"name": "Sophie van Dijk",
"cv_url": "<org-id>/dossiers/a91c…/cv.pdf",
"status": "active",
"current_stage_id": "b77f…",
"created_at": "2026-04-15T12:03:40Z"
}
}/dossiers/:idRetrieve a single dossier by ID.
curl https://leadgrid.io/api/v1/dossiers/a91c… \ -H "Authorization: Bearer lg_live_your_key"
{
"data": {
"id": "a91c…",
"type": "sales",
"name": "KLM — Cabin crew hiring",
"deal_size": 62000,
"current_stage_id": "b77f…"
}
}/dossiers/:idUpdate any subset of fields. Setting current_stage_id moves the dossier to a new stage and emits a dossier.stage_changed webhook.
| Field | Type | Description |
|---|---|---|
| name | string | Rename the dossier. |
| string | Update primary contact email. | |
| phone | string | Update phone. |
| company | string | Update company / employer. |
| role | string | Update role. |
| contact_person | string | Sales only. |
| deal_size | number | Sales only. |
| deal_currency | string | Sales only. |
| status | string | 'active', 'won', 'lost' or 'archived'. |
| assigned_to | uuid | Reassign to another member. Null to unassign. |
| intake_notes | string | Replace intake notes. |
| current_stage_id | uuid | Move to a new stage. Must belong to the dossier's flow. |
curl -X PATCH https://leadgrid.io/api/v1/dossiers/a91c… \
-H "Authorization: Bearer lg_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "current_stage_id": "d8e2…", "status": "active" }'{
"data": {
"id": "a91c…",
"current_stage_id": "d8e2…",
"status": "active"
}
}/dossiers/:idArchives the dossier (soft delete). Sets status to 'archived' and emits dossier.deleted. Data is preserved and can be restored via PATCH.
curl -X DELETE https://leadgrid.io/api/v1/dossiers/a91c… \ -H "Authorization: Bearer lg_live_your_key"
{
"data": {
"id": "a91c…",
"status": "archived"
}
}/dossiers/:id/cvUpload a PDF CV (max 10 MB) and attach it to an existing dossier. The upload replaces any previous CV. Accepts multipart/form-data with a 'cv' field, or application/pdf with the PDF bytes as the raw body. Emits dossier.updated.
# multipart/form-data curl -X POST https://leadgrid.io/api/v1/dossiers/a91c…/cv \ -H "Authorization: Bearer lg_live_your_key" \ -F "cv=@./resume.pdf" # raw application/pdf curl -X POST https://leadgrid.io/api/v1/dossiers/a91c…/cv \ -H "Authorization: Bearer lg_live_your_key" \ -H "Content-Type: application/pdf" \ --data-binary @./resume.pdf
{
"data": {
"id": "a91c…",
"cv_url": "<org-id>/dossiers/a91c…/cv.pdf"
}
}Flows
Flows are the pipelines dossiers move through. Each flow has ordered stages with optional deadlines and win probabilities.
/flowsList flows in your organization. Stages are nested and ordered by position.
| Field | Type | Description |
|---|---|---|
| type | string | Filter to 'candidate' or 'sales'. |
| page | integer | Page number. Default: 1. |
| per_page | integer | Items per page. Default: 25. |
curl "https://leadgrid.io/api/v1/flows?type=sales" \ -H "Authorization: Bearer lg_live_your_key"
{
"data": [
{
"id": "f01a…",
"name": "Sales Flow",
"type": "sales",
"is_default": true,
"stages": [
{
"id": "s1…",
"name": "Lead",
"position": 1,
"deadline_days": 3,
"win_probability": 14,
"color": "#FF5C35"
},
{
"id": "s2…",
"name": "Discovery",
"position": 2,
"deadline_days": 5,
"win_probability": 29,
"color": "#22C55E"
}
]
}
],
"meta": { "total": 1, "page": 1, "per_page": 25 }
}/flows/:id/stagesGet the stages for a single flow, sorted by position. Useful if you already know the flow_id and want just the stages.
curl https://leadgrid.io/api/v1/flows/f01a…/stages \ -H "Authorization: Bearer lg_live_your_key"
{
"data": [
{
"id": "s1…",
"name": "Lead",
"position": 1,
"deadline_days": 3,
"win_probability": 14
}
],
"meta": { "total": 6, "page": 1, "per_page": 6 }
}Notes
Notes are the timeline of updates attached to a dossier. They're sorted oldest-first and are always internal by default.
/dossiers/:id/notesList notes for a dossier, oldest first.
curl https://leadgrid.io/api/v1/dossiers/a91c…/notes \ -H "Authorization: Bearer lg_live_your_key"
{
"data": [
{
"id": "n1…",
"dossier_id": "a91c…",
"content": "Had a great first call — strong culture fit.",
"is_internal": true,
"created_at": "2026-04-14T09:12:30Z"
}
],
"meta": { "total": 3, "page": 1, "per_page": 25 }
}/dossiers/:id/notesAdd a new note to a dossier. Emits a note.created webhook.
| Field | Type | Description |
|---|---|---|
| content* | string | The note text. Cannot be empty. |
| is_internal | boolean | Default: true. Internal notes are not shared. |
curl -X POST https://leadgrid.io/api/v1/dossiers/a91c…/notes \
-H "Authorization: Bearer lg_live_your_key" \
-H "Content-Type: application/json" \
-d '{ "content": "Followed up by email." }'{
"data": {
"id": "n2…",
"dossier_id": "a91c…",
"content": "Followed up by email.",
"is_internal": true,
"created_at": "2026-04-15T12:04:10Z"
}
}Webhooks
Configure a webhook endpoint in Settings → API. LeadGrid sends a POST request with a JSON body when any of these events happen.
| Field | Type | Description |
|---|---|---|
| dossier.created | event | A new dossier was created (via API, UI or email). |
| dossier.updated | event | Any field on a dossier changed. Fires alongside stage_changed when applicable. |
| dossier.stage_changed | event | current_stage_id was updated. |
| dossier.deleted | event | Dossier was archived (status set to 'archived'). |
| note.created | event | A new note was added to a dossier. |
POST https://your-app.com/webhooks/leadgrid
Content-Type: application/json
X-LeadGrid-Signature: t=1713178230,v1=3b2c4f…
{
"id": "evt_…",
"type": "dossier.stage_changed",
"created_at": "2026-04-15T12:04:10Z",
"data": {
"id": "a91c…",
"current_stage_id": "d8e2…",
"status": "active"
}
}Signature verification
Each webhook request includes an X-LeadGrid-Signature header containing a timestamp and an HMAC-SHA256 signature of `${timestamp}.${body}` signed with your endpoint’s secret. Verify it before trusting the payload.
import crypto from "node:crypto";
export function verifyLeadGridSignature(
header: string,
body: string,
secret: string,
) {
const parts = Object.fromEntries(
header.split(",").map((p) => p.split("=")),
);
const { t, v1 } = parts as { t: string; v1: string };
const expected = crypto
.createHmac("sha256", secret)
.update(`${t}.${body}`)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(v1),
);
}Errors & rate limits
Errors use standard HTTP status codes. The body always contains an error object with a stable code and a human-readable message.
{
"error": {
"code": "not_found",
"message": "Dossier not found."
}
}| Field | Type | Description |
|---|---|---|
| unauthorized | 401 | Missing, malformed or invalid API key. Also returned for expired keys. |
| plan_required | 402 | Your organization is on Free or Pro. API access requires Growth. |
| forbidden | 403 | The API key doesn't include the required scope for this action. |
| not_found | 404 | The resource doesn't exist, or doesn't belong to your organization. |
| invalid_body | 400 | Missing required field, unknown value or malformed JSON. |
| rate_limited | 429 | You've exceeded the rate limit for your plan. Retry after the time in the Retry-After header. |
| internal | 500 | Unexpected server error. Safe to retry. |
X-RateLimit-Limit and X-RateLimit-Remaining headers so you can back off before hitting the cap.