API referencev1

Build on top of your LeadGrid pipelines.

Create dossiers, move them through stages, sync notes and react to events. One REST API, JSON in and out. Available on the Growth plan.

01: Start here

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.

Base URL
https://leadgrid.io/api/v1
Format
JSON (UTF-8)
Auth
Bearer API key
Availability
Growth plan

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.

02: Security

Authentication

Every request must carry an API key in the Authorization header. Keys start with lg_live_ and are scoped to one organization.

  1. 1Go to Settings → API and click Create API key.
  2. 2Pick the scopes you need (see table below) and optionally set an expiry. The full key is shown once . Copy it immediately.
  3. 3Send the key as a bearer token on every request.
curl
curl https://leadgrid.io/api/v1/dossiers \
  -H "Authorization: Bearer lg_live_your_key_here"
Available scopes
FieldTypeDescription
dossiers:readscopeList and retrieve dossiers.
dossiers:writescopeCreate, update and archive dossiers. Also required to move stages.
contacts:readscopeList and retrieve contacts, and read their dossier links.
contacts:writescopeCreate, update and delete contacts. Required alongside dossiers:write to link or unlink contacts on a dossier.
flows:readscopeList flows and read their stages.
notes:readscopeRead notes on dossiers.
notes:writescopeAdd new notes to dossiers.
Never expose your key in client-side code. The key has full access to your organization; treat it like a password. If a key leaks, delete it from Settings → API and rotate immediately.
03: Resources

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.

GET/dossiers

List dossiers in your organization. Supports filtering and pagination.

Query parameters
FieldTypeDescription
typestring'candidate' or 'sales'.
statusstring'active', 'won', 'lost' or 'archived'.
stage_iduuidReturn only dossiers currently in this stage.
pageinteger1-based page number. Default: 1.
per_pageintegerItems per page (max 100). Default: 25.
Example request
curl "https://leadgrid.io/api/v1/dossiers?type=sales&status=active" \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "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 }
}
POST/dossiers

Create 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. Pass an optional contact_id to link the dossier to an existing Contact instead of creating a new one. 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.

Request body
FieldTypeDescription
type*string'candidate' or 'sales'.
name*stringFor candidates: the person's name. For sales: deal or account name.
emailstringPrimary contact email.
phonestringPrimary contact phone.
companystringCandidate: current employer. Sales: target company.
rolestringCandidate: role they're applying for. Sales: role of the contact.
contact_iduuidLink the dossier to an existing Contact instead of creating a new one. Must belong to your organization.
flow_iduuidOverride the default flow. Must belong to your organization.
assigned_touuidUser ID of the member to assign this dossier to.
intake_notesstringDirector/intake notes shown in the dossier drawer.
contact_personstringSales only. Named contact at the target company.
deal_sizenumberSales only. Expected contract value.
deal_currencystringSales only. ISO 4217 currency code (e.g. 'EUR').
cvfile (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.
Example request
# 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"
Example response
{
  "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"
  }
}
GET/dossiers/:id

Retrieve a single dossier by ID.

Example request
curl https://leadgrid.io/api/v1/dossiers/a91c… \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": {
    "id": "a91c…",
    "type": "sales",
    "name": "KLM, Cabin crew hiring",
    "deal_size": 62000,
    "current_stage_id": "b77f…"
  }
}
PATCH/dossiers/:id

Update any subset of fields. Setting current_stage_id moves the dossier to a new stage and emits a dossier.stage_changed webhook.

Request body
FieldTypeDescription
namestringRename the dossier.
emailstringUpdate primary contact email.
phonestringUpdate phone.
companystringUpdate company / employer.
rolestringUpdate role.
contact_personstringSales only.
deal_sizenumberSales only.
deal_currencystringSales only.
statusstring'active', 'won', 'lost' or 'archived'.
assigned_touuidReassign to another member. Null to unassign.
intake_notesstringReplace intake notes.
current_stage_iduuidMove to a new stage. Must belong to the dossier's flow.
Example request
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" }'
Example response
{
  "data": {
    "id": "a91c…",
    "current_stage_id": "d8e2…",
    "status": "active"
  }
}
DELETE/dossiers/:id

Archives the dossier (soft delete). Sets status to 'archived' and emits dossier.deleted. Data is preserved and can be restored via PATCH.

Example request
curl -X DELETE https://leadgrid.io/api/v1/dossiers/a91c… \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": {
    "id": "a91c…",
    "status": "archived"
  }
}
POST/dossiers/:id/cv

Upload 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.

Example request
# 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
Example response
{
  "data": {
    "id": "a91c…",
    "cv_url": "<org-id>/dossiers/a91c…/cv.pdf"
  }
}
04: Resources

Contacts

Contacts are the people in your network: candidates, prospects, clients, suppliers and partners. A single Contact can carry multiple kinds at once and can be linked to any number of dossiers.

GET/contacts

List contacts in your organization. Supports filtering by kind, pool status and free-text search, plus pagination.

Query parameters
FieldTypeDescription
kindstringFilter by kind. Repeat the parameter to combine values: 'candidate', 'prospect', 'client', 'supplier' or 'partner'.
poolstring'in_pool' for contacts currently in the talent pool, 'expired' for contacts whose pool window has passed.
searchstringFree-text search over full_name, email, company and role.
pageinteger1-based page number. Default: 1.
per_pageintegerItems per page (max 100). Default: 25.
Example request
curl "https://leadgrid.io/api/v1/contacts?kind=candidate&kind=client&search=sophie" \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": [
    {
      "id": "c12a…",
      "full_name": "Sophie van Dijk",
      "email": "sophie@example.com",
      "phone": "+31 6 12345678",
      "linkedin_url": "https://linkedin.com/in/sophievandijk",
      "company": "Adyen",
      "role": "Senior Frontend Engineer",
      "city": "Amsterdam",
      "kind": ["candidate", "client"],
      "notes": null,
      "dossier_count": 2,
      "created_at": "2026-04-08T11:14:02Z"
    }
  ],
  "meta": { "total": 142, "page": 1, "per_page": 25 }
}
POST/contacts

Create a new contact. full_name is the only required field. If a contact with the same email already exists in your organization (case-insensitive), the API returns 409 with a duplicate_contact error.

Request body
FieldTypeDescription
full_name*stringThe contact's full name.
emailstringPrimary email. Must be unique per organization (case-insensitive). Returns 409 on conflict.
phonestringPrimary phone number.
linkedin_urlstringPublic LinkedIn profile URL.
companystringCurrent employer or account.
rolestringJob title or role.
citystringCity of residence.
kindstring[]Array of kinds: 'candidate', 'prospect', 'client', 'supplier', 'partner'. Defaults to an empty array.
notesstringFree-form internal notes shown on the contact drawer.
Example request
curl -X POST https://leadgrid.io/api/v1/contacts \
  -H "Authorization: Bearer lg_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "full_name": "Sophie van Dijk",
    "email": "sophie@example.com",
    "company": "Adyen",
    "role": "Senior Frontend Engineer",
    "city": "Amsterdam",
    "kind": ["candidate"]
  }'
Example response
{
  "data": {
    "id": "c12a…",
    "full_name": "Sophie van Dijk",
    "email": "sophie@example.com",
    "company": "Adyen",
    "role": "Senior Frontend Engineer",
    "city": "Amsterdam",
    "kind": ["candidate"],
    "dossier_count": 0,
    "created_at": "2026-04-30T08:21:14Z"
  }
}
GET/contacts/:id

Retrieve a single contact by ID, including their kinds and aggregated dossier counts.

Example request
curl https://leadgrid.io/api/v1/contacts/c12a… \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": {
    "id": "c12a…",
    "full_name": "Sophie van Dijk",
    "email": "sophie@example.com",
    "company": "Adyen",
    "role": "Senior Frontend Engineer",
    "city": "Amsterdam",
    "kind": ["candidate", "client"],
    "dossier_count": 2
  }
}
PATCH/contacts/:id

Partial update of any field on a contact. Send only the keys you want to change.

Request body
FieldTypeDescription
full_namestringRename the contact.
emailstringUpdate primary email. Still subject to the per-org uniqueness check.
phonestringUpdate phone number.
linkedin_urlstringUpdate LinkedIn URL.
companystringUpdate company.
rolestringUpdate role.
citystringUpdate city.
kindstring[]Replace the full kinds array. Send the complete set of kinds you want on the contact.
notesstringReplace internal notes.
Example request
curl -X PATCH https://leadgrid.io/api/v1/contacts/c12a… \
  -H "Authorization: Bearer lg_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "kind": ["candidate", "client"], "city": "Rotterdam" }'
Example response
{
  "data": {
    "id": "c12a…",
    "kind": ["candidate", "client"],
    "city": "Rotterdam"
  }
}
DELETE/contacts/:id

Hard delete the contact. Cascades to dossier_contacts links, but leaves the linked dossiers themselves intact.

Example request
curl -X DELETE https://leadgrid.io/api/v1/contacts/c12a… \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": {
    "id": "c12a…",
    "deleted": true
  }
}

Dossier links

A dossier-contact link associates a Contact with a dossier and records the role they play on that deal or hire (primary, hiring manager, decision maker, champion, gatekeeper, introducer or other). One dossier can have many contacts; one contact can appear on many dossiers.

GET/dossiers/:id/contacts

List the contacts linked to a dossier, including their role and whether they are the primary contact.

Example request
curl https://leadgrid.io/api/v1/dossiers/a91c…/contacts \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": [
    {
      "contact_id": "c12a…",
      "full_name": "Sophie van Dijk",
      "email": "sophie@example.com",
      "company": "Adyen",
      "role_on_dossier": "primary",
      "is_primary": true
    },
    {
      "contact_id": "c44b…",
      "full_name": "Mark de Vries",
      "email": "mark@rabobank.nl",
      "company": "Rabobank",
      "role_on_dossier": "decision_maker",
      "is_primary": false
    }
  ],
  "meta": { "total": 2, "page": 1, "per_page": 25 }
}
POST/dossiers/:id/contacts

Link an existing contact to a dossier with a role. Setting is_primary to true demotes any previous primary contact on that dossier.

Request body
FieldTypeDescription
contact_id*uuidThe contact to link. Must belong to your organization.
role_on_dossier*stringOne of 'primary', 'hiring_manager', 'decision_maker', 'champion', 'gatekeeper', 'introducer' or 'other'.
is_primarybooleanDefault: false. Setting true demotes the previous primary contact on this dossier.
Example request
curl -X POST https://leadgrid.io/api/v1/dossiers/a91c…/contacts \
  -H "Authorization: Bearer lg_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "c44b…",
    "role_on_dossier": "decision_maker",
    "is_primary": false
  }'
Example response
{
  "data": {
    "dossier_id": "a91c…",
    "contact_id": "c44b…",
    "role_on_dossier": "decision_maker",
    "is_primary": false
  }
}
DELETE/dossiers/:id/contacts/:contact_id

Unlink a contact from a dossier. The contact itself is preserved.

Example request
curl -X DELETE https://leadgrid.io/api/v1/dossiers/a91c…/contacts/c44b… \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": {
    "dossier_id": "a91c…",
    "contact_id": "c44b…",
    "unlinked": true
  }
}
05: Resources

Flows

Flows are the pipelines dossiers move through. Each flow has ordered stages with optional deadlines and win probabilities.

GET/flows

List flows in your organization. Stages are nested and ordered by position.

Query parameters
FieldTypeDescription
typestringFilter to 'candidate' or 'sales'.
pageintegerPage number. Default: 1.
per_pageintegerItems per page. Default: 25.
Example request
curl "https://leadgrid.io/api/v1/flows?type=sales" \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "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 }
}
GET/flows/:id/stages

Get the stages for a single flow, sorted by position. Useful if you already know the flow_id and want just the stages.

Example request
curl https://leadgrid.io/api/v1/flows/f01a…/stages \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "data": [
    {
      "id": "s1…",
      "name": "Lead",
      "position": 1,
      "deadline_days": 3,
      "win_probability": 14
    }
  ],
  "meta": { "total": 6, "page": 1, "per_page": 6 }
}
06: Resources

Notes

Notes are the timeline of updates attached to a dossier. They're sorted oldest-first and are always internal by default.

GET/dossiers/:id/notes

List notes for a dossier, oldest first.

Example request
curl https://leadgrid.io/api/v1/dossiers/a91c…/notes \
  -H "Authorization: Bearer lg_live_your_key"
Example response
{
  "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 }
}
POST/dossiers/:id/notes

Add a new note to a dossier. Emits a note.created webhook.

Request body
FieldTypeDescription
content*stringThe note text. Cannot be empty.
is_internalbooleanDefault: true. Internal notes are not shared.
Example request
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." }'
Example response
{
  "data": {
    "id": "n2…",
    "dossier_id": "a91c…",
    "content": "Followed up by email.",
    "is_internal": true,
    "created_at": "2026-04-15T12:04:10Z"
  }
}
07: Events

Webhooks

Configure a webhook endpoint in Settings → API. LeadGrid sends a POST request with a JSON body when any of these events happen.

Event types
FieldTypeDescription
dossier.createdeventA new dossier was created (via API, UI or email).
dossier.updatedeventAny field on a dossier changed. Fires alongside stage_changed when applicable.
dossier.stage_changedeventcurrent_stage_id was updated.
dossier.deletedeventDossier was archived (status set to 'archived').
note.createdeventA new note was added to a dossier.
Example payload
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.

node.js (verification)
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),
  );
}
08: Reference

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 shape
{
  "error": {
    "code": "not_found",
    "message": "Dossier not found."
  }
}
Common error codes
FieldTypeDescription
unauthorized401Missing, malformed or invalid API key. Also returned for expired keys.
plan_required402Your organization is on Free or Pro. API access requires Growth.
forbidden403The API key doesn't include the required scope for this action.
not_found404The resource doesn't exist, or doesn't belong to your organization.
invalid_body400Missing required field, unknown value or malformed JSON.
rate_limited429You've exceeded the rate limit for your plan. Retry after the time in the Retry-After header.
internal500Unexpected server error. Safe to retry.
Growth rate limit
600 req / min / key
429 response
Retry-After header in seconds
Every response includes X-RateLimit-Limit and X-RateLimit-Remaining headers so you can back off before hitting the cap.