Skip to main content
These recipes show how to chain API calls for complete recruiting workflows. Each recipe includes the exact endpoints, request bodies, and expected responses.

Manage project pipeline entries

Use this workflow when an integration needs to read candidates from a project, filter them by stage, add new leads to a project, or move existing leads through the pipeline.
1

Get the project stages

Pipeline entry filters use stage IDs, not stage names. Start by reading the project and storing the stage ID you need, for example the Sourced stage.
curl -X GET "https://app.leonar.app/api/v1/projects/project-uuid" \
  -H "Authorization: Bearer leo_your_api_key"
Response excerpt:
{
  "data": {
    "id": "project-uuid",
    "name": "Senior Frontend Engineers",
    "pipeline": {
      "stages": [
        {
          "id": "sourced-stage-uuid",
          "name": "Sourced",
          "position": 0,
          "system_category": "sourced",
          "entries_count": 12
        }
      ]
    }
  }
}
2

List only entries in a given stage

The project is selected by the URL path (/projects/{project_id}/entries). Add stage_id as a query parameter to return only entries currently in that stage for the same project. Values like Sourced are not accepted directly; use the stage UUID returned by GET /projects/{id}.
curl -X GET "https://app.leonar.app/api/v1/projects/project-uuid/entries?stage_id=sourced-stage-uuid&limit=100&offset=0" \
  -H "Authorization: Bearer leo_your_api_key"
3

Create or update the contact

If the lead is new, create a contact first:
curl -X POST "https://app.leonar.app/api/v1/contacts" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Sophie",
    "last_name": "Martin",
    "title": "Senior Software Engineer",
    "current_company": "Doctolib",
    "linkedin_profile": "https://www.linkedin.com/in/sophie-martin"
  }'
If the lead already exists and you only need to update their profile, use:
curl -X PUT "https://app.leonar.app/api/v1/contacts/contact-uuid" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Staff Software Engineer",
    "current_company": "Doctolib"
  }'
4

Add the contact to the project

Add the contact to the project pipeline. If stage_id is omitted, Leonar adds the contact to the first stage of the project pipeline.
curl -X POST "https://app.leonar.app/api/v1/projects/project-uuid/entries" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "contact-uuid",
    "stage_id": "sourced-stage-uuid"
  }'
5

Move an existing project lead to another stage

Use the pipeline entry ID from GET /projects/{id}/entries, then pass the target stage UUID.
curl -X PUT "https://app.leonar.app/api/v1/pipeline-entries/pipeline-entry-uuid" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "stage_id": "target-stage-uuid"
  }'

Source candidates and add to project

Find candidates on LinkedIn and add them to a recruiting project’s pipeline.
1

Get your connected LinkedIn account

curl -X GET "https://app.leonar.app/api/v1/connected-accounts" \
  -H "Authorization: Bearer leo_your_api_key"
Response — pick an account with api_status.recruiter: "active" or api_status.sales_navigator: "active":
{
  "data": [
    {
      "id": "acc-uuid-here",
      "name": "Alice Recruiter",
      "license_type": "recruiter",
      "api_status": {
        "classic": "active",
        "sales_navigator": "inactive",
        "recruiter": "active"
      }
    }
  ]
}
2

Look up location IDs (for LinkedIn filters)

curl -X GET "https://app.leonar.app/api/v1/sourcing/linkedin/locations?q=Paris&account_id=acc-uuid-here&api_type=recruiter" \
  -H "Authorization: Bearer leo_your_api_key"
Response:
{
  "data": [
    {"id": "105015875", "title": "Paris, Ile-de-France, France"},
    {"id": "104482823", "title": "Paris Area, France"}
  ]
}
3

Search LinkedIn profiles

LinkedIn pagination is cursor-based. To load more results, reuse the cursor returned by the previous response. Do not send only page: 2; that can return the same profiles as page 1.
curl -X POST "https://app.leonar.app/api/v1/sourcing/linkedin/search" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "project-uuid",
    "account_id": "acc-uuid-here",
    "job_titles": ["Software Engineer", "Backend Developer"],
    "location_ids": {"105015875": "Paris, France"},
    "years_experience": {"min": 3, "max": 10},
    "page": 1,
    "page_size": 25
  }'
Response — profiles with already_in_project: false can be added:
{
  "data": {
    "profiles": [
      {
        "profile_id": "urn:li:member:123456",
        "first_name": "Sophie",
        "last_name": "Martin",
        "headline": "Senior Software Engineer at Doctolib",
        "current_job": {"title": "Senior Software Engineer", "company_name": "Doctolib"},
        "already_in_project": false,
        "existing_contact_id": null
      }
    ],
    "total_count": 342,
    "filtered_count": 340,
    "has_more": true,
    "next_page": 2,
    "cursor": "eyJzdGFydCI6MjV9"
  }
}
4

Load the next LinkedIn page

Send the same search body with the returned cursor. The cursor is the value that advances the LinkedIn result set.
curl -X POST "https://app.leonar.app/api/v1/sourcing/linkedin/search" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "project-uuid",
    "account_id": "acc-uuid-here",
    "job_titles": ["Software Engineer", "Backend Developer"],
    "location_ids": {"105015875": "Paris, France"},
    "years_experience": {"min": 3, "max": 10},
    "cursor": "eyJzdGFydCI6MjV9",
    "page_size": 25
  }'
5

Add selected profiles to project

Pass the full profile objects directly from the search response. Include experiences, educations, skills, summary, and picture_url to create rich contact records:
curl -X POST "https://app.leonar.app/api/v1/sourcing/add-to-project" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": "project-uuid",
    "profiles": [
      {
        "profile_id": "urn:li:member:123456",
        "first_name": "Sophie",
        "last_name": "Martin",
        "headline": "Senior Software Engineer at Doctolib",
        "picture_url": "https://media.licdn.com/dms/image/example.jpg",
        "location": "Paris, Île-de-France, France",
        "linkedin_url": "https://www.linkedin.com/in/sophie-martin",
        "summary": "Experienced engineer with 8 years in full-stack development...",
        "current_job": {
          "title": "Senior Software Engineer",
          "company_name": "Doctolib"
        },
        "experiences": [
          {
            "title": "Senior Software Engineer",
            "company_name": "Doctolib",
            "start_date": "2021-03-01",
            "end_date": null,
            "description": "Leading backend team, Python/Django stack...",
            "is_current": true
          },
          {
            "title": "Software Engineer",
            "company_name": "Criteo",
            "start_date": "2018-01-01",
            "end_date": "2021-02-28",
            "description": "Built real-time bidding systems...",
            "is_current": false
          }
        ],
        "educations": [
          {
            "educational_establishment": "École Polytechnique",
            "diploma": "Master of Engineering",
            "specialization": "Computer Science",
            "start_date": "2014-09-01",
            "end_date": "2018-06-30"
          }
        ],
        "skills": ["Python", "Django", "TypeScript", "PostgreSQL"],
        "total_years_experience": 8.2
      }
    ]
  }'
The more fields you include, the richer the contact record. Fields like already_in_project and existing_contact_id from the search response are ignored — they are response-only metadata.
Response:
{
  "data": {
    "added": 1,
    "skipped": 0,
    "contact_ids": ["new-contact-uuid"],
    "skipped_profiles": [],
    "stage": {"id": "stage-uuid", "name": "Sourced", "category": "sourced"}
  }
}

Enrich a contact and find their email

1

Trigger enrichment

curl -X POST "https://app.leonar.app/api/v1/contacts/contact-uuid/enrich" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"type": "work_email"}'
Response:
{
  "data": {
    "request_id": "enrich-request-uuid",
    "type": "work_email",
    "status": "pending"
  }
}
2

Poll until completed

Enrichment is asynchronous. Poll every 5 seconds:
curl -X GET "https://app.leonar.app/api/v1/enrichment/enrich-request-uuid" \
  -H "Authorization: Bearer leo_your_api_key"
Response when completed:
{
  "data": {
    "id": "enrich-request-uuid",
    "contact_id": "contact-uuid",
    "enrichment_type": "work_email",
    "status": "completed",
    "result": {
      "email": "sophie@doctolib.com",
      "confidence": 0.95
    },
    "created_at": "2025-02-10T10:00:00Z",
    "completed_at": "2025-02-10T10:00:08Z"
  }
}
The email is automatically added to the contact record. No need to update the contact manually.

Enroll contacts in a sequence

1

List available sequences

curl -X GET "https://app.leonar.app/api/v1/sequences?status=active&limit=10" \
  -H "Authorization: Bearer leo_your_api_key"
2

Enroll contacts (simple format)

curl -X POST "https://app.leonar.app/api/v1/sequences/sequence-uuid/enroll" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_ids": [
      "contact-uuid-1",
      "contact-uuid-2",
      "contact-uuid-3"
    ]
  }'
3

Or enroll with custom variables

Sequence custom variables use fixed placeholder names: {{custom_variable_1}} through {{custom_variable_5}}. Add those placeholders in the sequence step content, then pass per-contact values in custom_variables when enrolling. Arbitrary names like project_name are rejected.Example sequence step:
Hi {{first_name}}, I thought the {{custom_variable_1}} role could be a fit because of {{custom_variable_2}}.
Example enrollment payload:
curl -X POST "https://app.leonar.app/api/v1/sequences/sequence-uuid/enroll" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contacts": [
      {
        "contact_id": "contact-uuid-1",
        "custom_variables": {
          "custom_variable_1": "Senior Frontend Engineer",
          "custom_variable_2": "your React component library work"
        }
      }
    ]
  }'
At send time, Leonar resolves the example step to something like:
Hi Ada, I thought the Senior Frontend Engineer role could be a fit because of your React component library work.
Response:
{
  "data": {
    "enrolled": 1,
    "skipped_blacklisted": 0,
    "skipped_already_enrolled": 0,
    "skipped_active_elsewhere": 0,
    "skipped_not_found": 0
  }
}

Create and manage a deal

1

Get pipeline stages

curl -X GET "https://app.leonar.app/api/v1/deal-pipelines" \
  -H "Authorization: Bearer leo_your_api_key"
2

Create a deal

curl -X POST "https://app.leonar.app/api/v1/deals" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Recruitment - 3 Senior Engineers",
    "company_id": "company-uuid",
    "pipeline_id": "pipeline-uuid",
    "amount": 45000,
    "currency": "EUR",
    "expected_close_date": "2025-03-15"
  }'
3

Link a contact to the deal

curl -X POST "https://app.leonar.app/api/v1/deals/deal-uuid/contacts" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"contact_id": "contact-uuid"}'
4

Move deal through stages

curl -X PUT "https://app.leonar.app/api/v1/deals/deal-uuid/stage" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"stage_id": "next-stage-uuid"}'
5

Close the deal

curl -X POST "https://app.leonar.app/api/v1/deals/deal-uuid/close" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "won",
    "signed_amount": 45000,
    "actual_close_date": "2025-03-10"
  }'

Search and tag contacts

1

Create a tag

curl -X POST "https://app.leonar.app/api/v1/tags" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name": "ai-sourced", "color": "#6366F1", "scope": "contact"}'
2

Search contacts

curl -X POST "https://app.leonar.app/api/v1/contacts/search" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "react typescript",
    "location": "Paris",
    "limit": 50
  }'
3

Tag matching contacts

For each matching contact:
curl -X POST "https://app.leonar.app/api/v1/contacts/contact-uuid/tags" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"tag_id": "tag-uuid"}'

Send a message to a contact

curl -X POST "https://app.leonar.app/api/v1/messages" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "contact-uuid",
    "channel": "email",
    "subject": "Exciting opportunity at Doctolib",
    "content": "Hi Sophie, I came across your profile..."
  }'
For LinkedIn messages, specify the channel and optionally the sender account:
curl -X POST "https://app.leonar.app/api/v1/messages" \
  -H "Authorization: Bearer leo_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "contact_id": "contact-uuid",
    "channel": "linkedin",
    "content": "Hi Sophie, I noticed your experience with React...",
    "sender_account_id": "account-uuid"
  }'