Endpoint index
Generated from openapi.yaml. Base URL: https://apiv2.lagrowthmachine.com/flow. Authenticate with Authorization: Bearer YOUR_API_KEY.
Campaigns
Section titled “Campaigns”GET /campaigns
Section titled “GET /campaigns”Get Campaigns
Use this endpoint to retrieve all your campaigns, with their status, audience, sending identity and a few key metrics.
Pagination
Section titled “Pagination”This endpoint supports pagination using the skip and limit query parameters:
limit→ number of items to return (required, 25 maximum)skip→ number of items to skip (used to navigate pages)
Examples:
- Page 1 →
?skip=0&limit=25 - Page 2 →
?skip=25&limit=25 - Page 3 →
?skip=50&limit=25
You can adjust these values depending on your pagination logic. total in the response is the number of campaigns matching your filters, use it to know when to stop.
Sorting
Section titled “Sorting”Campaigns are returned prioritised campaigns first, then by status (RUNNING, PAUSED, READY, CANCELED), then most recently launched first. When you filter on status=READY only, they are sorted by last modification date instead.
Filtering
Section titled “Filtering”status: one or several statuses, comma-separated (?status=RUNNING,PAUSED).search: case-insensitive match on the campaign name.
Any other query parameter is rejected with a 404 error.
Each item contains the fields documented below. Other internal fields may be present: they are not part of the contract and can change without notice. To read the settings of one campaign (autoRescheduleOutOfOffice, ignoreEmojiOnlyReply…), use Get Campaign.
| Parameter | In | Required | Description |
|---|---|---|---|
limit |
query | yes | Number of campaigns to return (25 max). |
skip |
query | no | Number of campaigns to skip (0 minimum). |
status |
query | no | Comma-separated list of statuses to keep, among RUNNING, READY, PAUSED, CANCELED. Values are not validated, an unknown status simply matches nothing. |
search |
query | no | Case-insensitive search on the campaign name. |
Response 200:
{ "statusCode": 200, "campaigns": [ { "id": "64b7f0c2a1d300000000000b", "name": "Email Only - 1 Follow-up 1 1", "description": "", "objective": null, "language": "english", "createdAt": 1791192988271, "modifiedAt": 1791192988273, "userId": "64b7f0c2a1d3000000000003", "status": "READY", "identity": { "id": "64b7f0c2a1d3000000000004", "firstname": "John", "lastname": "Doe", "gender": null, "jobTitle": null, "profilePicture": null }, "launchedByMemberId": null, "launchedAt": null, "channels": [ "LGM", "LINKEDIN", "GOOGLE" ], "slotIds": [], "hubspot": false, "pipedrive": false, "enrich": false, "isShared": false, "accessCode": null, "isHiddenTemplate": false, "alternateFrom": null, "crmSettings": { "exclusionEnabled": false, "excludedListIds": [], "pauseIfCrmHasDeal": false, "pauseOnLifecycleChange": { "enabled": false, "targetStages": [] }, "pauseIfInDynamicList": { "enabled": false, "listIds": [] } }, "autoRescheduleOutOfOffice": false, "lastActivity": null, "isPrioritised": false, "ctaBoost": null, "categoryIsAuto": null, "categoryIsLoading": null, "leadsCount": 0, "replyRatePercent": 0 } ], "total": 1}Errors: 401, 404, 429, 500.
GET /campaigns/public
Section titled “GET /campaigns/public”Get Public Campaigns
Lists the ready-made campaigns anyone can copy with Duplicate Campaign. The duplicated campaign has no audience and no sending identity yet: set them before launching it.
Each item has an origin:
catalog: official LGM strategy templates (sequence architectures such as “LinkedIn > Email - 3 Follow-ups”).creators: use-case templates shared by LGM or by other LGM users. They only appear withtype=public.
Use type to choose the set:
public(default): the full list, catalog then creators.recommended: a short curated catalog shortlist, the usual starting point.featured: catalog campaigns flagged as featured.
The list is not paginated. channels includes passive steps (a LinkedIn profile visit on an email sequence, for instance), so it does not tell which channel the sequence messages through; the description and name do.
| Parameter | In | Required | Description |
|---|---|---|---|
type |
query | no | Which set of campaigns to return. |
Response 200:
{ "statusCode": 200, "campaigns": [ { "id": "64b7f0c2a1d3000000000001", "name": "Email Only - 1 Follow-up 1", "description": "Simple Email outbound sequence, perfect if you want to keep it light and simple. Expect less conversion than multichannel sequences!", "channels": [ "LGM", "LINKEDIN", "GOOGLE" ], "tags": [ "HR" ], "order": 14, "origin": "catalog" }, { "id": "64b7f0c2a1d3000000000002", "name": "Email > Linkedin - 1 Follow-ups 1", "description": "Engage first via Emails, using LinkedIn only as a fallback. This sequence is perfect if you need to activate a high volume of leads per day", "channels": [ "LGM", "LINKEDIN", "GOOGLE" ], "tags": [ "OUTBOUND" ], "order": 23, "origin": "catalog" }, { "id": "64b7f0c2a1d3000000000003", "name": "Email > Linkedin > Twitter - 2 Follow-ups 1", "description": "Engage first via Emails, using LinkedIn as primary fallback, then Twitter if they haven’t replied. This sequence is perfect if you need to activate a high volume of leads per day.", "channels": [ "LGM", "LINKEDIN", "GOOGLE" ], "tags": [ "HR" ], "order": 22, "origin": "catalog" } ]}Errors: 400, 401, 429, 500.
GET /campaigns/{campaignId}
Section titled “GET /campaigns/{campaignId}”Get Campaign
Use this endpoint to get one campaign: its status, audience, sending identity and settings.
This endpoint takes no query parameter: any query parameter is rejected with a 404 error.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | Campaign ID (24-character hex string). |
Response 200:
{ "statusCode": 200, "campaign": { "id": "64b7f0c2a1d300000000000b", "name": "Email Only - 1 Follow-up 1 1", "description": "", "language": "english", "createdAt": 1791192988271, "modifiedAt": 1791192988273, "launchedAt": null, "userId": "64b7f0c2a1d3000000000003", "status": "READY", "channels": [ "LGM", "LINKEDIN", "GOOGLE" ], "identityId": "64b7f0c2a1d3000000000004", "identityFirstname": "John", "identityLastname": "Doe", "launchedByMemberId": null, "converted": 0, "activated": 0, "hubspot": false, "pipedrive": false, "enrich": false, "autoRescheduleOutOfOffice": true, "ignoreEmojiOnlyReply": false, "objective": null }}Errors: 401, 404, 429, 500.
GET /campaigns/{campaignId}/stats
Section titled “GET /campaigns/{campaignId}/stats”Get Campaign Stats
Use this endpoint to get the stats of one campaign: audience, replies, LinkedIn relations, per-channel metrics, lead statuses and per-message (template) metrics.
All ...Percent fields are integers between 0 and 100. If your plan does not include lead qualification, the detailed status.won and status.lost counters and percentages are returned as 0 (only their total is kept).
This endpoint takes no query parameter: any query parameter is rejected with a 404 error.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | Campaign ID (24-character hex string). |
Response 200:
{ "statusCode": 200, "engagementStats": { "campaignId": "64b7f0c2a1d300000000000b", "audienceSize": 0, "notActivated": 0, "converted": 0, "completed": 0, "started": 0, "skipped": 0, "skippedPercent": 0, "replies": { "contacted": 0, "replied": 0, "linkedinContacted": 0, "emailContacted": 0, "linkedinReplied": 0, "emailReplied": 0, "contactedPercent": 0, "totalReplyPercent": 0, "contactedReplyPercent": 0, "linkedinContactedReplyPercent": 0, "emailContactedReplyPercent": 0 }, "relations": { "activated": true, "requestSent": 0, "relations": 0, "newRelations": 0, "alreadyConnected": 0, "requestSentPercent": 0, "relationsPercent": 0, "totalRelationsPercent": 0, "newRelationsPercent": 0, "alreadyConnectedPercent": 0 }, "channel": { "email": { "activated": true, "sent": 0, "received": 0, "opened": 0, "replied": 0, "clicked": 0, "bounced": 0, "won": 0, "sentPercent": 0, "receivedPercent": 0, "openedPercent": 0, "repliedPercent": 0, "clickedPercent": 0, "bouncedPercent": 0, "wonPercent": 0 }, "linkedin": { "activated": true, "contactRequest": { "activated": true, "sent": 0, "newRelations": 0, "alreadyContact": 0, "sentPercent": 0, "newRelationsPercent": 0, "alreadyContactPercent": 0 }, "message": { "sent": 0, "replied": 0, "won": 0, "sentPercent": 0, "repliedPercent": 0, "wonPercent": 0 } }, "twitter": { "activated": false, "sent": 0, "retweet": 0, "favorited": 0, "followed": 0, "followBack": 0, "followedPercent": 0, "followBackPercent": 0 }, "enrich": { "activated": false, "proUpgrade": false, "businessUpgrade": true, "enriched": 0, "found": { "profile": 0, "profileSearch": 0, "website": 0, "proEmail": 0, "persoEmail": 0, "phone": 0, "companyPhone": 0, "twitter": 0, "profilePercent": 0, "profileSearchPercent": 0, "profileOnProfileSearchPercent": 0, "websitePercent": 0, "proEmailPercent": 0, "persoEmailPercent": 0, "phonePercent": 0, "companyPhonePercent": 0, "twitterPercent": 0 }, "enrichedPercent": 0 }, "call": { "activated": false, "contacted": 0, "decisive": 0, "unreachable": 0, "won": 0, "decisivePercent": 0, "unreachablePercent": 0 } }, "status": { "activated": { "total": 0, "activated": 0, "enriched": 0, "contacted": 0, "completed": 0, "outOfOffice": 0, "activatedPercent": 0, "enrichedPercent": 0, "contactedPercent": 0, "completedPercent": 0, "audiencePercent": 0, "contactedAudiencePercent": 0, "outOfOfficePercent": 0, "notActivated": 0, "notActivatedPercent": 0 }, "replied": { "total": 0, "toQualify": 0, "wrongTiming": 0, "toReview": 0, "toCall": 0, "inviteNote": 0, "customMessage": 0, "toQualifyPercent": 0, "wrongTimingPercent": 0, "audiencePercent": 0, "activatedPercent": 0, "contactedPercent": 0, "toReviewPercent": 0, "inviteNotePercent": 0, "customMessagePercent": 0 }, "won": { "total": 0, "interested": 0, "callBooked": 0, "negotiating": 0, "readyToBuy": 0, "converted": 0, "interestedPercent": 0, "callBookedPercent": 0, "negotiatingPercent": 0, "readyToBuyPercent": 0, "convertedPercent": 0, "audiencePercent": 0, "repliedPercent": 0 }, "lost": { "total": 0, "wrongTarget": 0, "notInterested": 0, "alreadyEquipped": 0, "unsubscribed": 0, "wrongTargetPercent": 0, "notInterestedPercent": 0, "alreadyEquippedPercent": 0, "unsubscribedPercent": 0, "audiencePercent": 0, "repliedPercent": 0 } }, "templates": [ { "sent": 0, "opened": 0, "clicked": 0, "replied": 0, "bounced": 0, "openedPercent": 0, "clickedPercent": 0, "repliedPercent": 0, "bouncedPercent": 0, "won": 0, "channel": "GOOGLE", "createdAt": 1791192988676, "newHtml": "<p>Hi <var name=\"firstname\" default=\"there\"/>, a quick question about Acme.</p>", "name": "GOOGLE message - step 4", "subjectNewHtml": "", "note": "", "templateId": "64b7f0c2a1d300000000000d", "type": "TEXT", "position": 0, "isCustomMessage": false } ], "objective": null }}Errors: 401, 404, 429, 500.
GET /campaigns/{campaignId}/statsleads
Section titled “GET /campaigns/{campaignId}/statsleads”Get Campaign Leads Stats
Use this endpoint to get the stats of every lead of one campaign: status in the campaign, LinkedIn relation, counts of emails, LinkedIn messages and tweets, and the lead tag.
Pagination
Section titled “Pagination”Each request returns 25 leads maximum, most recently added leads first.
- To get the first 25 leads, do not specify any query parameter.
- To get the 25 next leads, use
getLeadsAfterwith theidof the last lead of the previous page (?getLeadsAfter=<lastLead.id>). hasMoretells whether more leads follow,hasLesswhether leads precede the first one returned.countis the total number of leads in the campaign audience.
getLeadsAfter and getLeadsBefore are the only accepted query parameters: any other one is rejected with a 404 error. If the campaign does not exist in your workspace or has no audience, the response is an empty list (count: 0).
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | Campaign ID (24-character hex string). |
getLeadsAfter |
query | no | Lead ID. Returns the leads that come after this one (next page). |
getLeadsBefore |
query | no | Lead ID. Returns leads that come before this one, starting from the first lead of the campaign (see the known issue in the description of hasLess). |
Response 200:
{ "leads": [ { "firstname": "Jane", "lastname": "Smith", "companyName": "Acme", "companyUrl": "https://acme.com", "phone": null, "companyPhone": null, "jobTitle": "CTO", "profilePicture": null, "note": null, "status": "STARTED", "id": "64b7f0c2a1d3000000000007", "email": "jane.smith@acme.com", "linkedinUrl": "https://www.linkedin.com/in/jane-smith-acme", "gender": null, "industry": null, "relationStatus": "NOT_SENT", "acceptationDate": null, "replied": 0, "action_todo": null, "email_sent": 0, "email_opened": 0, "email_clicked": 0, "email_replied": 0, "linkedin_visit": 0, "linkedin_dm_sent": 0, "linkedin_audio_sent": 0, "linkedin_replied": 0, "linkedin_relation_status": "NOT_SENT", "twitter_tweeted": 0, "twitter_retweeted": 0, "twitter_favourited": 0, "twitter_followed": 0, "twitter_unfollowed": 0, "twitter_dm_sent": 0, "tweet_sent": 0 }, { "firstname": "John", "lastname": "Doe", "companyName": "Acme", "companyUrl": "https://acme.com", "phone": null, "companyPhone": null, "jobTitle": "Head of Sales", "profilePicture": null, "note": null, "status": "STARTED", "id": "64b7f0c2a1d3000000000006", "email": "john.doe@acme.com", "linkedinUrl": null, "gender": null, "industry": null, "relationStatus": "NOT_SENT", "acceptationDate": null, "replied": 0, "action_todo": null, "email_sent": 0, "email_opened": 0, "email_clicked": 0, "email_replied": 0, "linkedin_visit": 0, "linkedin_dm_sent": 0, "linkedin_audio_sent": 0, "linkedin_replied": 0, "linkedin_relation_status": "NOT_SENT", "twitter_tweeted": 0, "twitter_retweeted": 0, "twitter_favourited": 0, "twitter_followed": 0, "twitter_unfollowed": 0, "twitter_dm_sent": 0, "tweet_sent": 0 } ], "count": 2, "hasMore": false, "hasLess": false, "statusCode": 200}Errors: 401, 404, 429, 500.
GET /campaigns/{campaignId}/messages
Section titled “GET /campaigns/{campaignId}/messages”Get Campaign Messages
Returns the text messages (templates) configured in a campaign’s sequence: emails, LinkedIn messages and tweets. Audio and call steps are not returned.
Each message has two representations:
contentHtml/subjectHtml: rendered output, read-only.newHtml/subjectNewHtml: editable source in the LGM editor format. Send these back to Edit Campaign Message to change a message.
order is the index of the message in this response, not its position in the sequence: use Get Campaign Steps for the sequence order.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | The campaign ID (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": [ { "id": "64b7f0c2a1d300000000000d", "type": "TEXT", "channel": "GOOGLE", "contentHtml": "<span>Hi </span><span>{{firstname 'there'}}</span><span>, following up.</span><br>", "subjectHtml": "", "newHtml": "<p>Hi <var name=\"firstname\" default=\"there\"/>, following up.</p>", "subjectNewHtml": "", "order": 0, "active": true } ], "total": 1}Errors: 400, 401, 404, 429, 500.
GET /campaigns/{campaignId}/steps
Section titled “GET /campaigns/{campaignId}/steps”Get Campaign Steps
Lists the steps of a campaign sequence that carry a message (email, LinkedIn message, tweet, Twitter DM), in sequence order.
Use it to find a step’s stepId and to spot empty steps (hasMessage: false), then fill them with Add Campaign Step Message. A step that already has a message is edited with Edit Campaign Message, using the step’s templateId.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | The campaign ID (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": [ { "stepId": "00000000-0000-4000-8000-00000000000c", "type": "GOOGLE_SEND_EMAIL", "channel": "GOOGLE", "position": 4, "hasMessage": false, "templateId": null, "templateName": null } ], "total": 1}Errors: 400, 401, 404, 429, 500.
POST /campaigns/{campaignId}/settings
Section titled “POST /campaigns/{campaignId}/settings”Update Campaign
Use this endpoint to update the settings of one campaign.
Send only the settings you want to change. At least one of name, audienceId, enrich, autoRescheduleOutOfOffice, hubspot, pipedrive, skipAlreadyContacted, ignoreEmojiOnlyReply is required. The other settings of the campaign are left unchanged. Any other field is rejected with a 400 error.
Some settings depend on the campaign status:
name: must not be used by another campaign of your workspace.audienceId: can only be changed while the campaign has not been started yet (statusREADY).hubspot/pipedrive: cannot be changed while the campaign isRUNNING(pause it first), and enabling them requires the CRM sync in your plan.skipAlreadyContacted: can only be changed while the campaign isREADYorPAUSED.ignoreEmojiOnlyReply: can only be changed while the campaign isREADYorPAUSED.enrichandautoRescheduleOutOfOfficecan be changed in any status.
Sending a setting with its current value is accepted in any status.
The response returns the updated settings of the campaign.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | Campaign ID (24-character hex string). |
Request body:
{ "name": "Acme outbound (renamed)", "audienceId": "64b7f0c2a1d3000000000005"}Response 200:
{ "statusCode": 200, "data": { "id": "64b7f0c2a1d300000000000b", "name": "Acme outbound (renamed)", "audience": { "id": "64b7f0c2a1d3000000000005", "name": "Acme prospects", "size": 2 }, "enrich": false, "autoRescheduleOutOfOffice": true, "hubspot": false, "pipedrive": false, "skipAlreadyContacted": false, "ignoreEmojiOnlyReply": false, "status": "READY" }}Errors: 400, 401, 403, 404, 429, 500.
POST /campaigns/{campaignId}/status
Section titled “POST /campaigns/{campaignId}/status”Update Campaign Status
Use this endpoint to pause or resume one campaign.
Send the target status:
PAUSED: pauses the campaign. Only aRUNNINGcampaign can be paused. Scheduled actions of the campaign leads are put on hold.RUNNING: resumes the campaign. Only aPAUSEDcampaign can be resumed. Actions of the campaign leads are scheduled again. The same plan rules as in the app apply (running campaigns limit, identity subscription, CRM / custom campaign / custom messages / A/B testing permissions).
This endpoint does not launch a campaign for the first time: a READY campaign must be launched from the LGM app.
The call is not idempotent: pausing an already paused campaign, or resuming an already running campaign, returns a 400 error and nothing is changed.
Pausing a campaign synced with your CRM can take a few seconds (one timeline event is created per lead already contacted). If your client times out, check the campaign status with Get Campaign instead of retrying.
Edits made while the campaign is paused are not retroactive: they only apply to the actions still pending once the campaign is resumed.
The response returns the id, name and new status of the campaign.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | Campaign ID (24-character hex string). |
Request body:
{ "status": "PAUSED"}Response 200:
{ "statusCode": 200, "data": { "id": "65a1b2c3d4e5f60718293a4b", "name": "My Campaign", "status": "PAUSED" }}Errors: 400, 401, 404, 429, 500.
POST /campaigns/{campaignId}/duplicate
Section titled “POST /campaigns/{campaignId}/duplicate”Duplicate a campaign
Creates a new draft copy of a campaign: its sequence and every step’s message (with new message IDs).
The copy is created with status READY and is not launched.
Works with:
- your own campaigns (IDs from
GET /campaigns): the copy keeps the source identity, audience and language; - public catalog templates (IDs from
GET /campaigns/public): the copy uses the last identity you used, your last language for that identity and no audience.
The copy is named after the source campaign followed by a number (for example Acme outbound Q4 12).
Duplicating a campaign with a custom sequence requires a plan that includes custom sequences.
Typical flow: duplicate the campaign, read its messages with GET /campaigns/{campaignId}/messages, then
rewrite them with POST /campaigns/{campaignId}/messages/{templateId}.
This endpoint takes no request body. Unknown query parameters are rejected with a 400.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | ID of the campaign to duplicate (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": { "id": "64b7f0c2a1d300000000000b", "name": "Email Only - 1 Follow-up 1 1", "status": "READY" }}Errors: 400, 401, 403, 404, 429, 500.
POST /campaigns/{campaignId}/messages/{templateId}
Section titled “POST /campaigns/{campaignId}/messages/{templateId}”Edit a campaign message
Replaces the content of an existing message of a campaign step. Get the campaignId and the
templateId (the id of each message) from GET /campaigns/{campaignId}/messages. To fill a step that has
no message yet, use POST /campaigns/{campaignId}/steps/{stepId}/message instead.
The content is validated server-side; a malformed message is rejected with a 400 and nothing is saved.
Not retroactive. On a campaign that has already started (RUNNING or PAUSED), leads who already
received this step keep the message they got; the new content only applies to actions still pending.
The newHtml format
Section titled “The newHtml format”- The body is a sequence of block tags (
<p>…</p>,<ul><li>…</li></ul>,<h2>…</h2>); wrap all text in blocks. - Send the whole string on a single line, with no whitespace between top-level blocks (
</p><p>, never</p> <p>). - Personalization variables only as
<var name="firstname"/>, optionally with a fallback:<var name="firstname" default="there"/>.{{…}},{…},%…%or[…]are not variables. - Inline tags:
<b>,<i>,<u>,<s>,<br/>(line break inside a paragraph),<a href="https://…" track="true">text</a>,<spin>Hi|Hello</spin>(text only),<signature/>(emails). - Escape
<as<and&as&.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | ID of the campaign the message belongs to (24-character hex string). |
templateId |
path | yes | ID of the message to edit (24-character hex string), the id returned by GET /campaigns/{campaignId}/messages. |
Request body:
{ "newHtml": "<p>Hi <var name=\"firstname\" default=\"there\"/>, a quick question about Acme.</p>"}Response 200:
{ "statusCode": 200, "data": { "id": "64b7f0c2a1d300000000000d", "channel": "GOOGLE", "contentHtml": "<span>Hi </span><span>{{firstname 'there'}}</span><span>, a quick question about Acme.</span><br>", "subjectHtml": "" }}Errors: 400, 401, 404, 429, 500.
POST /campaigns/{campaignId}/steps/{stepId}/message
Section titled “POST /campaigns/{campaignId}/steps/{stepId}/message”Add a message to an empty campaign step
Creates a new message and links it to a campaign step that has no message yet (a step returned by
GET /campaigns/{campaignId}/steps with hasMessage: false). The channel is inferred from the step.
If the step already has a message, the call is rejected: edit it with
POST /campaigns/{campaignId}/messages/{templateId} instead.
The content is validated server-side with the same rules as the edit endpoint (see the newHtml format
described on POST /campaigns/{campaignId}/messages/{templateId}).
Not retroactive. On a campaign that has already started (RUNNING or PAUSED), leads who already
passed this step are not sent the new message; it only applies to actions still pending.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | ID of the campaign the step belongs to (24-character hex string). |
stepId |
path | yes | ID of the step, the stepId returned by GET /campaigns/{campaignId}/steps. |
Request body:
{ "newHtml": "<p>Hi <var name=\"firstname\" default=\"there\"/>, following up.</p>"}Response 200:
{ "statusCode": 200, "data": { "id": "64b7f0c2a1d300000000000d", "stepId": "00000000-0000-4000-8000-00000000000c", "channel": "GOOGLE", "contentHtml": "<span>Hi </span><span>{{firstname 'there'}}</span><span>, following up.</span><br>", "subjectHtml": "" }}Errors: 400, 401, 404, 429, 500.
POST /campaigns/{campaignId}/share-by-code
Section titled “POST /campaigns/{campaignId}/share-by-code”Share a campaign by code
Generates an access code for one of your campaigns. Anyone with an LGM account and the code can preview
the campaign (GET /campaigns/accessible/{code}) and copy it into their own workspace
(POST /campaigns/duplicate/accessible/{code}). The preview exposes the sequence and its messages only;
leads and stats are never shared.
The code is made of 24 random hex characters, a -, then a compact form of the campaign name.
Idempotent: if the campaign is already shared, the existing code is returned. Requires a plan that includes campaign sharing. This endpoint takes no request body.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | ID of the campaign to share (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": { "campaignId": "64b7f0c2a1d3e4f5a6b7c8d9", "code": "9f3c2a1b7e6d5c4b3a291807-acmeOutboundQ4", "shared": true }}Errors: 400, 401, 404, 429, 500.
POST /campaigns/{campaignId}/stop-sharing-by-code
Section titled “POST /campaigns/{campaignId}/stop-sharing-by-code”Stop sharing a campaign by code
Revokes the access code of a campaign. The code stops working immediately; copies already made by other accounts are not affected. Idempotent: calling it on a campaign that is not shared returns the same response. This endpoint takes no request body.
| Parameter | In | Required | Description |
|---|---|---|---|
campaignId |
path | yes | ID of the campaign (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": { "campaignId": "64b7f0c2a1d300000000000a", "code": null, "shared": false }}Errors: 400, 401, 404, 429, 500.
GET /campaigns/accessible/{code}
Section titled “GET /campaigns/accessible/{code}”Preview a campaign shared by code
Returns a preview of a campaign shared with an access code: name, description, language, the inventory of
sequence actions and the content of its messages. Use it to review a shared campaign before copying it with
POST /campaigns/duplicate/accessible/{code}. Leading and trailing spaces in the code are ignored.
| Parameter | In | Required | Description |
|---|---|---|---|
code |
path | yes | The access code received from the campaign owner. |
Response 200:
{ "statusCode": 200, "data": { "name": "Acme outbound Q4", "description": "Three-touch sequence for heads of sales.", "language": "english", "stepCount": 3, "actions": [ { "type": "LINKEDIN_ADD_CONTACT_WITH_NOTE", "channel": "LINKEDIN", "count": 1 }, { "type": "GOOGLE_SEND_EMAIL", "channel": "GOOGLE", "count": 2 } ], "messages": [ { "type": "TEXT", "channel": "GOOGLE", "subjectHtml": "Quick question for <span class=\"variable\">{{company}}</span>", "contentHtml": "<p>Hi <span class=\"variable\">{{firstname 'there'}}</span>,</p><p>How does <span class=\"variable\">{{company}}</span> handle outbound today?</p>", "subjectNewHtml": "<p>Quick question for <var name=\"company\"/></p>", "newHtml": "<p>Hi <var name=\"firstname\" default=\"there\"/>,</p><p>How does <var name=\"company\"/> handle outbound today?</p>" } ], "callScriptCount": 0, "voiceMessageCount": 0 }}Errors: 401, 404, 429, 500.
POST /campaigns/duplicate/accessible/{code}
Section titled “POST /campaigns/duplicate/accessible/{code}”Duplicate a campaign shared by code
Copies a campaign shared with an access code into your workspace, as a new draft campaign (status READY,
not launched, no audience). The copy uses the last identity you used and your last language for that
identity. Your workspace needs at least one identity.
Then edit its messages like any of your campaigns. This endpoint takes no request body.
| Parameter | In | Required | Description |
|---|---|---|---|
code |
path | yes | The access code received from the campaign owner. |
Response 200:
{ "statusCode": 200, "data": { "id": "65a1b2c3d4e5f60718293a6d", "name": "Acme outbound Q4 3", "status": "READY" }}Errors: 401, 404, 429, 500.
Audiences
Section titled “Audiences”GET /audiences
Section titled “GET /audiences”List audiences
Returns all audiences of the authenticated workspace (deleted audiences excluded).
No parameter needed. Each audience carries its id, name, type, size (number of leads) and import state.
Response 200:
{ "statusCode": 200, "audiences": [ { "id": "64b7f0c2a1d3000000000005", "name": "Acme prospects", "description": "", "userId": "64b7f0c2a1d3000000000003", "modifiedAt": 1791192987296, "createdAt": 1791192987020, "size": 1, "targetSize": null, "sourceUrl": null, "type": "CLASSIC", "importationPending": false, "sourceListId": null, "refreshPeriod": 0, "importStatus": null, "deletionPending": null, "filterLeads": null, "enrichType": null, "preEnrich": null, "importationStartedAt": null, "excludeContactedLeads": false, "hasAutoImport": false, "notContactedSourceCampaignId": null, "signals": [] } ]}Errors: 401, 404, 429.
POST /audiences
Section titled “POST /audiences”Create audience from linkedin url
Import leads into your LGM audiences by providing a LinkedIn Regular search URL, a Sales Navigator search or list URL, a LinkedIn post URL, a company followers URL or a LinkedIn event URL. You must also provide the identity to impersonate for the search query and specify the name of the audience to populate.
If no audience matches audience (by name, then by ID), a new one is created. The import runs asynchronously on the identity’s LinkedIn account: the response only confirms that it was queued. Follow its progress with GET /flow/audiences/{audienceId}/detail.
- The identity’s LinkedIn account must be connected and the LGM extension (widget) open while the import runs.
- For a post URL, set
linkedinPostCategoryto import the people who liked (like) or commented (comment) the post. - For an event URL, only an identity registered to the event can see its attendees; any other identity imports 0 leads.
autoImportis only available for Sales Navigator, post, followers and event URLs, not for a Regular search URL.
More information: https://www.youtube.com/watch?v=nt54qlEbZJM
Request body:
{ "audience": "Acme prospects", "linkedinUrl": "https://www.linkedin.com/sales/search/people?query=(keywords%3Aacme)", "identityId": "60a1b2c3d4e5f60012345678", "excludeContactedLeads": true}Response 200:
{ "statusCode": 200}Errors: 401, 404, 429.
POST /audiences/create
Section titled “POST /audiences/create”Create Audience
Creates an empty audience (type CLASSIC). Add leads to it with POST /flow/leads.
To create an audience from a LinkedIn URL (search or post), use POST /flow/audiences instead.
Request body:
{ "name": "Acme prospects"}Response 200:
{ "statusCode": 200, "audience": { "id": "64b7f0c2a1d3000000000005", "name": "Acme prospects" }}Errors: 400, 401, 429.
GET /audiences/{audienceId}/leads
Section titled “GET /audiences/{audienceId}/leads”Get Audience Leads
Returns a paginated list of leads belonging to a specific audience, with the full lead record. Use skip and limit to page through the audience; total is the number of leads in the audience.
Every field is always present in the response. Values depend on the lead’s enrichment level: a lead that was never enriched returns empty strings (and null for relationsCount, emailStatus, emailFoundBy). The example response is truncated to customAttribute1–customAttribute3 for readability; all 20 are always returned.
| Parameter | In | Required | Description |
|---|---|---|---|
audienceId |
path | yes | The audience ID (24-char hex ObjectId). |
skip |
query | no | Number of leads to skip. |
limit |
query | no | Max leads to return. |
Response 200:
{ "statusCode": 200, "data": [ { "id": "64b7f0c2a1d3000000000006", "firstname": "John", "lastname": "Doe", "companyName": "Acme", "jobTitle": "Head of Sales", "linkedinUrl": "", "proEmail": "john.doe@acme.com", "phone": "", "status": "unknown", "location": "", "industry": "", "shortBio": "", "companyUrl": "https://acme.com", "persoEmail": "", "gender": "", "twitter": "", "relationsCount": null, "enrichStatus": "Not enriched", "emailStatus": null, "emailFoundBy": null, "signal": "", "crm_id": "", "audiences": [ "Acme prospects" ], "customAttribute1": "", "customAttribute2": "", "customAttribute3": "", "customAttribute4": "", "customAttribute5": "", "customAttribute6": "", "customAttribute7": "", "customAttribute8": "", "customAttribute9": "", "customAttribute10": "", "customAttribute11": "", "customAttribute12": "", "customAttribute13": "", "customAttribute14": "", "customAttribute15": "", "customAttribute16": "", "customAttribute17": "", "customAttribute18": "", "customAttribute19": "", "customAttribute20": "" } ], "total": 1}Errors: 400, 401, 404, 429.
GET /audiences/{audienceId}/detail
Section titled “GET /audiences/{audienceId}/detail”Get Audience Detail
Returns detailed information about a specific audience. Use it to follow an import started with POST /flow/audiences: importStatus reports how many leads were screened.
| Parameter | In | Required | Description |
|---|---|---|---|
audienceId |
path | yes | The audience ID (24-char hex ObjectId). |
Response 200:
{ "statusCode": 200, "data": { "id": "64b7f0c2a1d3000000000005", "name": "Acme prospects", "description": "", "size": 1, "type": "CLASSIC", "createdAt": "2026-10-05T09:36:27.020Z", "modifiedAt": "2026-10-05T09:36:27.296Z", "importStatus": "" }}Errors: 400, 401, 404, 429.
POST /leads
Section titled “POST /leads”Create or Update a Lead
Creates a lead (or updates it when it already exists).
Identifying the lead
Section titled “Identifying the lead”At least one of the following is required: leadId, proEmail, persoEmail, linkedinUrl, twitter, crm_id, OR firstname + lastname + (companyName or companyUrl).
- With
leadId, that exact lead is updated (404 if it does not exist). - Otherwise the lead is matched against existing leads on these identifiers. A duplicate is merged or ignored depending on the workspace’s “merge duplicate leads” setting; the response
messagesays which.
Audience
Section titled “Audience”audience (optional) is the name (or ID) of the target audience. If no audience matches, a new one is created. If omitted, the lead is created with no audience.
Profile fields
Section titled “Profile fields”All optional, max 255 characters unless specified: firstname, lastname, gender (man|woman), bio (max 5000), companyName, companyUrl, jobTitle, profilePicture, linkedinUrl, twitter, proEmail, persoEmail, industry, phone, crm_id, location, relationsCount (integer).
crm_id links the lead to a contact of the CRM connected to the workspace (HubSpot or Pipedrive).
Custom fields
Section titled “Custom fields”customAttribute1…customAttribute20 (string, max 2500).
note (string, max 1500) — free-text note displayed on the lead profile. Providing it replaces the existing note; omitting it keeps it.
Options
Section titled “Options”excludeContactedLeads(boolean, defaultfalse) — whentrue, a lead already contacted in one of your campaigns is ignored (nothing is created nor updated).enrichData(object) — enrichment data already available on the caller side, keyed by field name.enrichStatus(string).
Empty values ("", null, 0, false) are ignored.
Request body:
{ "audience": "Acme prospects", "firstname": "John", "lastname": "Doe", "proEmail": "john.doe@acme.com", "companyName": "Acme", "companyUrl": "https://acme.com", "jobTitle": "Head of Sales"}Response 200:
{ "statusCode": 200, "message": "Lead created and added to the audience. Added to existing audience.", "leadId": "64b7f0c2a1d3000000000006"}Errors: 401, 404, 429.
GET /leads/search
Section titled “GET /leads/search”Search Lead
Searches for a lead in the authenticated workspace and returns the first match, with the campaigns it is involved in and its most recent activity.
Query params — at least one criterion required
Section titled “Query params — at least one criterion required”leadId— direct lookup by idcrmId— direct lookup by external CRM idlinkedinUrl/linkedinId/linkedinPublicId/linkedinLongPublicId— LinkedIn lookupemail— lookup by email (pro or perso)firstname+lastname+ (companyNameorcompanyUrl) — first and last name alone do not match
Matching priority
Section titled “Matching priority”leadIdcrmId- Any of the other criteria: the first lead matching at least one of them is returned.
Response
Section titled “Response”lead— the matched lead, withinvolvedCampaigns: running, paused or canceled campaigns whose audience contains the lead.recentLogs— the most recent successful action per campaign (up to 20 campaigns).
Returns 404 when no lead matches.
| Parameter | In | Required | Description |
|---|---|---|---|
leadId |
query | no | Lead id (24 chars). |
crmId |
query | no | Contact id in the connected CRM. |
firstname |
query | no | Lead firstname (with lastname and companyName or companyUrl). |
lastname |
query | no | Lead lastname. |
companyName |
query | no | Lead company name. |
companyUrl |
query | no | Lead company URL. |
linkedinUrl |
query | no | Lead LinkedIn profile URL (with or without trailing slash). |
linkedinId |
query | no | Lead LinkedIn numeric id. |
linkedinPublicId |
query | no | Lead LinkedIn public id (slug). |
linkedinLongPublicId |
query | no | Lead LinkedIn long public id. |
email |
query | no | Lead email (pro or perso). |
Response 200:
{ "statusCode": 200, "lead": { "firstname": "John", "lastname": "Doe", "id": "64b7f0c2a1d3000000000006", "linkedinId": null, "gender": null, "shortBio": null, "linkedinLongPublicId": null, "lastActions": { "last": [], "status": [], "lastStatus": [] }, "companyName": "Acme", "companyUrl": "https://acme.com", "createdAt": 1791192987067, "modifiedAt": 1791192987067, "jobTitle": "Head of Sales", "profilePicture": null, "linkedinUrl": null, "salesNavUrl": null, "enriched": null, "userId": "64b7f0c2a1d3000000000003", "twitter": null, "proEmail": "john.doe@acme.com", "persoEmail": null, "relationsCount": null, "audiences": [ "64b7f0c2a1d3000000000005" ], "addedAtByAudience": [], "industry": null, "phone": null, "companyPhone": null, "note": null, "location": null, "audiencesWithName": [], "pausedCampaigns": [], "involvedCampaigns": [], "involvedCampaignsLogs": [], "hubspot": {}, "pipedrive": {}, "lifecycle": null, "tags": [], "campaigns": [], "enrichData": null, "enrichStatus": null, "deleted": false, "migrationDone": null, "customAttribute1": null, "customAttribute2": null, "customAttribute3": null, "customAttribute4": null, "customAttribute5": null, "customAttribute6": null, "customAttribute7": null, "customAttribute8": null, "customAttribute9": null, "customAttribute10": null, "customAttribute11": null, "customAttribute12": null, "customAttribute13": null, "customAttribute14": null, "customAttribute15": null, "customAttribute16": null, "customAttribute17": null, "customAttribute18": null, "customAttribute19": null, "customAttribute20": null }, "recentLogs": []}Errors: 401, 404, 429.
POST /leads/status
Section titled “POST /leads/status”Update lead Status
Use this endpoint for updating lead status in campaign.
You need to specify at least one Lead field (persoEmail, proEmail, linkedinUrl, twitter, crm_id) or firstname + lastname + (companyUrl or companyName), or you can specify its ID in the body with the param leadId.
Campaigns
Section titled “Campaigns”campaign is a campaign name or ID, an array of them, or "all" for every non-stopped campaign whose audience contains the lead. Campaigns that are stopped or do not contain the lead are skipped; when none matches, logs is empty.
Statuses
Section titled “Statuses”PAUSED,RESUME— pause or resume the lead in the campaign.STOPPED— end the campaign for the lead.CONVERTED— mark the lead as converted and end the campaign.SUBSCRIBED— resubscribe a lead that was unsubscribed.- A lead tag:
NOT_ACTIVATED,SKIPPED,ACTIVATED,ENRICHED,CONTACTED,COMPLETED_WITHOUT_REPLY,TO_QUALIFY,OUT_OF_OFFICE,WRONG_TIMING,INTERESTED,NOT_INTERESTED,CALL_BOOKED,NEGOTIATING,READY_TO_BUY,CONVERTED,UNSUBSCRIBED,WRONG_TARGET,ALREADY_EQUIPPED. Tagging the leadINTERESTED,CALL_BOOKED,NEGOTIATING,READY_TO_BUYorCONVERTEDalso converts it;TO_QUALIFY,WRONG_TIMING,UNSUBSCRIBED,WRONG_TARGET,NOT_INTERESTEDorALREADY_EQUIPPEDalso ends the campaign for it. - Aliases:
COMPLETED_NO_REPLY(=COMPLETED_WITHOUT_REPLY),REPLIED(=TO_QUALIFY),CANNOT_CONTACT(=UNSUBSCRIBED).
memberEmail picks the member on whose behalf a SUBSCRIBED change is made (default: the workspace owner); from (lead or member) records who asked for it.
More information: https://www.youtube.com/watch?v=s4jevOQR4aY
Request body:
{ "campaign": "Email Only - 1 Follow-up 1 1", "status": "PAUSED", "proEmail": "john.doe@acme.com"}Response 200:
{ "logs": [], "statusCode": 200}Errors: 401, 404, 429.
POST /leads/removefromaudience
Section titled “POST /leads/removefromaudience”Remove lead from audiences
Removes a lead from one or more audiences.
audience(required) —"all", an audience name or ID, or an array of names or IDs.- At least one lead identifier:
crm_id,proEmail,persoEmail,linkedinUrl,twitter, orfirstname+lastname+ (companyNameorcompanyUrl).leadIdis not accepted here.
If the lead is not present in the audience, the operation is silently ignored. A single audience name that does not exist returns 404; unknown names in an array are ignored.
Request body:
{ "audience": [ "Acme prospects" ], "proEmail": "john.doe@acme.com"}Response 200:
{ "statusCode": 200}Errors: 401, 404, 429.
GET /leads/{leadId}/logs
Section titled “GET /leads/{leadId}/logs”Get Lead Logs
Returns a paginated list of activity logs for a specific lead, most recent first. total is the number of logs before pagination.
| Parameter | In | Required | Description |
|---|---|---|---|
leadId |
path | yes | The lead ID (24-char hex ObjectId). |
identityId |
query | no | Filter logs by identity ID (24-char hex ObjectId). |
skip |
query | no | Number of logs to skip. |
limit |
query | no | Max logs to return. |
Response 200:
{ "statusCode": 200, "data": []}Errors: 400, 401, 404, 429.
GET /leads/{leadId}/conversations
Section titled “GET /leads/{leadId}/conversations”Get Lead Conversations
Returns all inbox conversations associated with a specific lead. Read the messages of a conversation with GET /flow/conversations/{conversationId}/messages.
| Parameter | In | Required | Description |
|---|---|---|---|
leadId |
path | yes | The lead ID (24-char hex ObjectId). |
identityId |
query | no | Filter conversations by identity ID (24-char hex ObjectId). |
Response 200:
{ "statusCode": 200, "data": [], "total": 0}Errors: 400, 401, 404, 429.
POST /leads/enrich
Section titled “POST /leads/enrich”Enrich Lead
Starts an enrichment request. Depending on enrichType, it returns a pro email, a phone number, LinkedIn profile fields, or email and LinkedIn together.
Body — at least one identifier required
Section titled “Body — at least one identifier required”leadId(string, 24 chars) — id of an existing lead; the enriched fields are written on the lead.- OR
linkedinUrl(https,linkedin.com, max 500). - OR
firstname+lastname(+companyName/companyUrl).
enrichType
Section titled “enrichType”Drives what we enrich and how much it costs.
| Value | What it does | Credits (max) | Needs |
|---|---|---|---|
EMAIL_ENRICH (default) |
Finds the pro email and its status | 5 | leadId, or linkedinUrl, or firstname + lastname + (companyName or companyUrl) |
LINKEDIN_ENRICH |
Fetches the LinkedIn profile fields (job title, company, location…) | 1 | leadId or linkedinUrl |
FULL_ENRICH |
LinkedIn profile + email | 5 | leadId or linkedinUrl |
PHONE_ENRICH |
Finds a phone number (not available during the trial) | 20 | leadId |
The credits column is the maximum charged per request. The balance is checked before the request starts: with fewer credits than this amount, the call fails with 400. Without a leadId, the email is only charged when one is found, and a result already known to LGM is returned immediately (cached) without being charged. Your balance: GET /flow/credits.
Enrichment requires a plan with enrichment; during a trial it requires the external enrichment option.
polling(default) — immediate response withenrichRequestIdandstatus: pending. PollGET /flow/leads/enrich/{enrichRequestId}.sync— waits up to 60 seconds for the result.webhook— immediate response withstatus: accepted; the result is POSTed towebhookUrl(public HTTPS URL) once ready, as{ enrichRequestId, status, email?, emailStatus?, phone?, provider? }withstatusin upper case (ENRICHED,NOT_FOUND,ERROR…). Up to 3 attempts.
An enrichment request can be read for 24 hours.
Response shape
Section titled “Response shape”EMAIL_ENRICHandFULL_ENRICHresults includeemail+emailStatusonce enriched.LINKEDIN_ENRICHandFULL_ENRICHresults includelinkedinProfile. With aleadId, the enriched fields are also written on the lead (visible via the lead read endpoints).PHONE_ENRICHresults includephone.
Request body:
{ "leadId": "64b7f0c2a1d3000000000006", "enrichType": "EMAIL_ENRICH"}Response 200:
{ "statusCode": 200, "enrichRequestId": "00000000-0000-4000-8000-000000000010", "status": "pending"}Errors: 400, 401, 403, 404, 429.
GET /leads/enrich/{enrichRequestId}
Section titled “GET /leads/enrich/{enrichRequestId}”Get Enrich Result
Retrieves the result of an enrichment request previously created with POST /flow/leads/enrich (any mode). Requests are kept for 24 hours.
Possible statuses
Section titled “Possible statuses”pending— still running, poll again later.enriched— done; the result fields are in the response.not_found— nothing was found.error— the enrichment failed (for example not enough credits when it ran).expired— asynccall timed out. If the result arrived afterwards, it is returned (and was not charged); otherwise no data is returned.
email and emailStatus are returned for EMAIL_ENRICH and FULL_ENRICH, linkedinProfile for LINKEDIN_ENRICH and FULL_ENRICH, phone for PHONE_ENRICH.
| Parameter | In | Required | Description |
|---|---|---|---|
enrichRequestId |
path | yes | ID returned by POST /flow/leads/enrich. |
Response 200:
{ "statusCode": 200, "status": "pending"}Errors: 401, 404, 429.
POST /posts/enrich
Section titled “POST /posts/enrich”Enrich LinkedIn Post
Fetches the content and engagement of a public LinkedIn post from its URL: text, author, reaction counts, comments and reposts, posting date and URN.
Costs 1 credit per successful call. The balance is checked first (400 when it is too low) and the credit is only charged when the post is fetched. Requires a plan with enrichment; during a trial it requires the external enrichment option.
The result is returned synchronously. No campaign or identity is needed.
Request body:
{ "postUrl": "https://www.linkedin.com/posts/bricemaur_we-now-connect-to-a-crm-we-never-built-an-share-7511039697201238019-3Fr-/"}Response 200:
{ "statusCode": 200, "post": { "isBrandPartnership": false, "text": "We now connect to a CRM we never built an integration for 🔥\n\nHere's how that happened, and why I stopped treating the integration list as a roadmap problem.\n\nLGM has deep native syncs with HubSpot and Pipedrive. Attio isn't one of them, and people ask for it. Building a native integration for every CRM on the market is weeks of dev each, and that's weeks we'd rather spend elsewhere.\n\nSo I built it in n8n instead. When a conversation starts with a lead in LGM, the workflow checks Attio for that person on email, first name and last name. No match on any of the three and it creates the record. A match and it updates with the LinkedIn data, then stores the conversation.\n\nSame result as a native sync from where the user sits. Three hours to point it at the next CRM.\n\nI've said for a while that the upstream tools are complementary and interchangeable. The enrichment tool you love today gets swapped next year. What shouldn't move is the layer running your outreach and tying it back to revenue in your CRM.\n\nThat only holds if the layer actually talks to what you already use. Not eventually, not once it clears the roadmap, this week!\n\nMultiple templates are live: the CRM sync, reply listening, lead enrichment, and one that pauses a lead the second they book on your Calendly.\n\nWhich CRM should I do next? Tell me and I'll build it", "totalReactionCount": 11, "likeCount": 10, "praiseCount": 1, "commentsCount": 2, "shareUrl": "https://www.linkedin.com/posts/bricemaur_we-now-connect-to-a-crm-we-never-built-an-activity-7511039698820112385-Tbnu", "postedAt": "5d", "postedDate": "2026-09-30 12:30:12.568 +0000 UTC", "postedDateTimestamp": 1790771412568, "urn": "7511039698820112385", "shareUrn": "7511039697201238019", "author": { "firstname": "Brice", "lastname": "Maurln", "headline": "CEO at LGM | I ran outbound for clients for 9 years before building the platform I wished I had", "url": "https://www.linkedin.com/in/bricemaur", "avatar": "https://media.licdn.com/dms/image/v2/D4E03AQH2ULJ_2bf7pQ/profile-displayphoto-crop_800_800/B4EZ_WMW_4H0AI-/0/1786004993149?e=1792627200&v=beta&t=81H9QIMfxxQhSSsSyl94s5HAtn6NYGBfLmfw5mjhtW0" }, "image": [ { "url": "https://media.licdn.com/dms/image/v2/D4E10AQG-r930nLvvGQ/image-shrink_800/B4EaDySy8nGwAs-/0/1790771409394?e=1791878400&v=beta&t=hVHUXQADjuJ36lsG_zHiICjurlAdBwmLv4sSU2u3GMk", "width": 0, "height": 0 } ] }}Errors: 400, 401, 403, 404, 429.
POST /inbox/linkedin
Section titled “POST /inbox/linkedin”Send A Linkedin Message To A Lead
Sends a LinkedIn text or voice message to a lead from one of your connected LinkedIn identities.
You must provide either a leadId or a linkedinUrl, along with the identityId and the memberId performing the action.
Important rules
Section titled “Important rules”- You must provide at least one of:
leadIdorlinkedinUrl. - You must provide at least one of:
messageoraudioUrl. WhenaudioUrlis set, a voice message is sent. identityIdandmemberIdare always required.
memberIdmust be retrieved with List members. It must belong to your workspace.identityIdmust match a connected LinkedIn identity in your account (see List identities).- The lead is looked up by
leadId, or bylinkedinUrlamong your existing leads. The lead is not created if it does not exist. - A conversation between this lead and this identity must already exist, otherwise the request fails with
Conversation not found. - A
200means the message has been queued for sending, not that LinkedIn has delivered it. If delivery fails, the message shows up with statusSEND_FAILEDin Get conversation messages. - Identities running through the browser extension (not LinkedIn cloud) need the extension to be connected,
otherwise the request fails with
Widget running identity ... need to be connected.
⚠️ Warning: Be Responsible with Your API Usage
Please ensure that your scripts are properly controlled and not unintentionally looping over this endpoint. Uncontrolled or excessive calls may affect your experience and platform stability. La Growth Machine is not responsible for accidental or abusive usage caused by misconfigured automations or scripts.
Request body:
{ "identityId": "6650a1b2c3d4e5f6a7b8c901", "memberId": "6650a1b2c3d4e5f6a7b8c902", "leadId": "6650a1b2c3d4e5f6a7b8c903", "message": "Hi John, are you available to chat this week?"}Response 200:
{ "statusCode": 200, "lead": { "id": "6650a1b2c3d4e5f6a7b8c903", "firstname": "John", "lastname": "Doe", "gender": "man", "shortBio": "Head of Sales at Acme", "jobTitle": "Head of Sales", "companyName": "Acme", "companyUrl": "https://acme.com", "industry": "Software", "location": "Paris, France", "linkedinUrl": "https://www.linkedin.com/in/john-doe-acme", "salesNavUrl": "https://www.linkedin.com/sales/people/ACwAAA0000000,NAME_SEARCH", "proEmail": "john.doe@acme.com", "twitter": null, "phone": null, "companyPhone": null, "note": null, "userId": "6650a1b2c3d4e5f6a7b8c900", "audiences": [ "6650a1b2c3d4e5f6a7b8c910" ], "involvedCampaigns": [ { "_id": "6650a1b2c3d4e5f6a7b8c920", "name": "Q3 outreach", "status": "RUNNING", "launchedAt": 1719820800000, "tag": "TO_QUALIFY" } ], "tags": [ { "campaignId": "6650a1b2c3d4e5f6a7b8c920", "tag": "TO_QUALIFY", "status": "REPLIED" } ], "reply": { "repliedAt": 1720000000000, "content": "Yes, ok for me", "identityId": "6650a1b2c3d4e5f6a7b8c901", "channel": "LINKEDIN" }, "lastMessageSentAt": 1720100000000, "createdAt": 1719000000000, "modifiedAt": 1720100000000, "customAttribute1": null }}Errors: 400, 401, 403, 404, 429, 500.
POST /inbox/email
Section titled “POST /inbox/email”Send An Email Message To A Lead
Sends an email to a lead from one of your connected email identities, as a reply in an existing email thread.
Important rules
Section titled “Important rules”- You must provide at least one of
leadIdorleadEmail. - You must provide
replyInLastThread: trueor areplyToMessageId:replyInLastThread: truereplies in the most recent email thread of the conversation. The subject is set toRe: <subject of that thread>, whateversubjectyou send.replyToMessageIdreplies to a specific message (anidfrom Get conversation messages).
- Sending a brand new email (only a
subject, no thread to reply to) is not supported through the API: the request fails withemail or replyMessageId parameter needed.
identityIdmust match a connected email identity in your account.- The lead is looked up by
leadId, or byleadEmailamong your existing leads. - A conversation between this lead and this identity must already exist. With
replyInLastThread, it must also already contain an email. - The email is sent on behalf of the default member of your workspace.
memberIdis not accepted on this endpoint.
⚠️ Warning: Be Responsible with Your API Usage
Please ensure that your scripts are properly controlled and not unintentionally looping over this endpoint. Uncontrolled or excessive calls may affect your experience and platform stability. La Growth Machine is not responsible for accidental or abusive usage caused by misconfigured automations or scripts.
Request body:
{ "message": { "html": "<p>Hi John,</p><p>Just following up on my last email.</p>", "text": "Hi John, just following up on my last email." }, "identityId": "6650a1b2c3d4e5f6a7b8c901", "leadEmail": "john.doe@acme.com", "replyInLastThread": true}Response 200:
{ "statusCode": 200, "lead": { "id": "6650a1b2c3d4e5f6a7b8c903", "firstname": "John", "lastname": "Doe", "gender": "man", "shortBio": "Head of Sales at Acme", "jobTitle": "Head of Sales", "companyName": "Acme", "companyUrl": "https://acme.com", "industry": "Software", "location": "Paris, France", "linkedinUrl": "https://www.linkedin.com/in/john-doe-acme", "salesNavUrl": "https://www.linkedin.com/sales/people/ACwAAA0000000,NAME_SEARCH", "proEmail": "john.doe@acme.com", "twitter": null, "phone": null, "companyPhone": null, "note": null, "userId": "6650a1b2c3d4e5f6a7b8c900", "audiences": [ "6650a1b2c3d4e5f6a7b8c910" ], "involvedCampaigns": [ { "_id": "6650a1b2c3d4e5f6a7b8c920", "name": "Q3 outreach", "status": "RUNNING", "launchedAt": 1719820800000, "tag": "TO_QUALIFY" } ], "tags": [ { "campaignId": "6650a1b2c3d4e5f6a7b8c920", "tag": "TO_QUALIFY", "status": "REPLIED" } ], "reply": { "repliedAt": 1720000000000, "content": "Yes, ok for me", "identityId": "6650a1b2c3d4e5f6a7b8c901", "channel": "LINKEDIN" }, "lastMessageSentAt": 1720100000000, "createdAt": 1719000000000, "modifiedAt": 1720100000000, "customAttribute1": null }}Errors: 400, 401, 403, 404, 429, 500.
GET /inboxWebhooks
Section titled “GET /inboxWebhooks”List Inbox Webhooks
Lists the inbox webhooks configured in your workspace, with their IDs, names, target URLs and campaign filters.
Response 200:
[ { "id": "00000000-0000-4000-8000-00000000000f", "url": "https://example.com/lgm-docs-webhook", "key": "Acme prospects", "type": "INBOX_EVENT", "campaigns": [ "all" ], "createdAt": "2026-10-05T09:36:29.532Z" }]Errors: 400, 401, 403, 429, 500.
POST /inboxWebhooks
Section titled “POST /inboxWebhooks”Create Inbox Event Webhook
Registers a webhook that receives real-time inbox events: every new LinkedIn or email message, sent or received, in the conversations of your workspace.
url must be publicly accessible and respond to POST requests with a 200 OK.
If campaigns is omitted, the webhook receives messages from all campaigns (["all"]). To filter, pass the IDs of the
campaigns to follow: an event is sent when the lead is in at least one of them. Do not send an empty array: a webhook
with campaigns: [] never fires.
Payload sent to your URL
Section titled “Payload sent to your URL”LGM sends a POST with a JSON body about one minute after a new message (LinkedIn or email, sent or received) is
recorded. If several messages arrive for the same lead within that delay, a single call is made, for the latest one.
The request carries an Authorization: Bearer <token> header with a token generated for the webhook.
The body contains the latest message at the root, the lead in lead, and the lead’s 20 latest LinkedIn and email
messages (all identities, newest first) in messages:
{ "messageChannel": "LINKEDIN", "messageBody": "Thanks for reaching out, let's talk next week.", "messageId": "6650a1b2c3d4e5f6a7b8c950", "conversationId": "6650a1b2c3d4e5f6a7b8c940", "externalConversationId": "2-ZmFrZS10aHJlYWQtaWQ=", "leadId": "6650a1b2c3d4e5f6a7b8c903", "identityId": "6650a1b2c3d4e5f6a7b8c901", "userId": "6650a1b2c3d4e5f6a7b8c900", "createdAt": 1720000000000, "sent": false, "received": true, "campaignId": "6650a1b2c3d4e5f6a7b8c920", "campaignName": "Q3 outreach", "lead": { "id": "6650a1b2c3d4e5f6a7b8c903", "firstName": "John", "lastName": "Doe", "jobTitle": "Head of Sales", "gender": "man", "shortBio": "Head of Sales at Acme", "dateOfBirth": null, "email": "john.doe@acme.com", "persoEmail": null, "picture": null, "note": null, "companyName": "Acme", "companyWebsite": "https://acme.com", "industry": "Software", "linkedin": "https://www.linkedin.com/in/john-doe-acme", "twitter": null, "phone": null, "location": "Paris, France", "customAttribute1": null, "customAttribute20": null, "crmId": null }, "messages": [ { "messageChannel": "LINKEDIN", "messageBody": "Thanks for reaching out, let's talk next week.", "messageId": "6650a1b2c3d4e5f6a7b8c950", "conversationId": "6650a1b2c3d4e5f6a7b8c940", "createdAt": 1720000000000, "sent": false, "received": true } ]}messageChannel:LINKEDINorEMAIL.messageSubjectis only present for emails.- Fields without a value are omitted:
messageSubjecton LinkedIn,attachmentswhen there is none,campaignIdandcampaignNameoutside campaigns. sent/received: direction of the message. Both arefalsewhile an outgoing message is still being sent or failed.createdAt: Unix timestamp in milliseconds.leadholdscustomAttribute1tocustomAttribute20(shortened above) andcrmId(HubSpot or Pipedrive ID).- Each item of
messageshas the same message fields as the root (withoutlead).
Request body:
{ "url": "https://example.com/lgm-docs-webhook", "name": "Acme prospects", "campaigns": [ "all" ]}Response 200:
{ "id": "00000000-0000-4000-8000-00000000000f", "url": "https://example.com/lgm-docs-webhook", "key": "Acme prospects", "type": "INBOX_EVENT", "campaigns": [ "all" ], "audiences": [ "all" ], "tags": [], "createdAt": "2026-10-05T09:36:29.532Z"}Errors: 400, 401, 403, 429, 500.
DELETE /inboxWebhooks/{webhookId}
Section titled “DELETE /inboxWebhooks/{webhookId}”Delete Inbox Webhooks
Deletes an inbox webhook by its webhookId. Once deleted, no more inbox events are sent to its URL.
The response is {"success": true} even when no webhook has this ID: check with
List inbox webhooks if needed.
| Parameter | In | Required | Description |
|---|---|---|---|
webhookId |
path | yes | Webhook id returned at creation or by the list. |
Response 200:
{ "success": true}Errors: 400, 401, 403, 429, 500.
Conversations
Section titled “Conversations”POST /inbox/conversations/note
Section titled “POST /inbox/conversations/note”Edit Inbox Conversation Note
Writes the note (post-it) attached to an inbox conversation.
Finding the conversation
Section titled “Finding the conversation”Provide conversationId, or identify the lead with leadId, email or linkedinUrl. When you identify the lead and it has
several conversations (one per identity), narrow it down with identityId, campaignId or campaignName
(the campaign’s identity is used), otherwise the request fails with Multiple conversations found ... Be more specific.
replace(default): the note becomesnote. An empty string clears the note.append:noteis added after the existing note, separated by a line-- YYYY-MM-DD HH:mm --(UTC).
A note is limited to 1,500 characters. In append mode, when the result is longer, the oldest characters are dropped and
truncated is true.
Request body:
{ "conversationId": "6650a1b2c3d4e5f6a7b8c940", "note": "Call ASAP", "mode": "append"}Response 200:
{ "success": true, "data": { "conversationId": "6650a1b2c3d4e5f6a7b8c940", "leadId": "6650a1b2c3d4e5f6a7b8c903", "note": "Phone number found\n-- 2026-02-11 04:48 --\nCall ASAP", "mode": "append", "truncated": false, "noteLength": 51, "timestamp": "2026-02-11T04:48:37.200Z" }}Errors: 400, 401, 403, 404, 429, 500.
POST /inbox/conversations/archive
Section titled “POST /inbox/conversations/archive”Archive conversation
Archives a conversation, removing it from the active inbox. Reversible with Unarchive conversation.
Provide conversationId, or identify the lead with leadId, email or linkedinUrl. When the lead has several
conversations (one per identity), narrow it down with identityId, campaignId or campaignName.
Request body:
{ "conversationId": "6650a1b2c3d4e5f6a7b8c940"}Response 200:
{ "success": true, "data": { "conversationId": "6650a1b2c3d4e5f6a7b8c940", "leadId": "6650a1b2c3d4e5f6a7b8c903", "archived": true, "timestamp": "2026-06-04T12:51:10.015Z" }}Errors: 400, 401, 403, 404, 429, 500.
POST /inbox/conversations/unarchive
Section titled “POST /inbox/conversations/unarchive”Unarchive conversation
Moves an archived conversation back to the active inbox.
Provide conversationId, or identify the lead with leadId, email or linkedinUrl. When the lead has several
conversations (one per identity), narrow it down with identityId, campaignId or campaignName.
Request body:
{ "conversationId": "6650a1b2c3d4e5f6a7b8c940"}Response 200:
{ "success": true, "data": { "conversationId": "6650a1b2c3d4e5f6a7b8c940", "leadId": "6650a1b2c3d4e5f6a7b8c903", "archived": false, "timestamp": "2026-06-04T12:51:10.015Z" }}Errors: 400, 401, 403, 404, 429, 500.
POST /inbox/conversations/snooze
Section titled “POST /inbox/conversations/snooze”Snooze conversation
Snoozes a conversation until snoozeUntil: it leaves the active inbox and comes back at that date.
Reversible with Unsnooze conversation.
Provide conversationId, or identify the lead with leadId, email or linkedinUrl. When the lead has several
conversations (one per identity), narrow it down with identityId, campaignId or campaignName.
A conversation that is already snoozed cannot be snoozed again: unsnooze it first.
Request body:
{ "conversationId": "6650a1b2c3d4e5f6a7b8c940", "snoozeUntil": "2026-06-12T09:00:00.000Z"}Response 200:
{ "success": true, "data": { "conversationId": "6650a1b2c3d4e5f6a7b8c940", "leadId": "6650a1b2c3d4e5f6a7b8c903", "snoozed": true, "snoozeUntil": "2026-06-12T09:00:00.000Z", "timestamp": "2026-06-04T13:02:08.983Z" }}Errors: 400, 401, 403, 404, 429, 500.
POST /inbox/conversations/unsnooze
Section titled “POST /inbox/conversations/unsnooze”Unsnooze conversation
Brings a snoozed conversation back to the active inbox right away.
Provide conversationId, or identify the lead with leadId, email or linkedinUrl. When the lead has several
conversations (one per identity), narrow it down with identityId, campaignId or campaignName.
Request body:
{ "conversationId": "6650a1b2c3d4e5f6a7b8c940"}Response 200:
{ "success": true, "data": { "conversationId": "6650a1b2c3d4e5f6a7b8c940", "leadId": "6650a1b2c3d4e5f6a7b8c903", "snoozed": false, "snoozeUntil": null, "timestamp": "2026-06-04T13:05:32.746Z" }}Errors: 400, 401, 403, 404, 429, 500.
GET /conversations/search
Section titled “GET /conversations/search”Search Conversations
Searches and filters the inbox conversations of your workspace: free text, lead, identity, campaign, audience, channel, direction of the last message, dates and flags. Filters combine with AND.
Returns conversation IDs and metadata only, no lead names or message text: get the messages with Get conversation messages.
Pagination
Section titled “Pagination”Results are sorted by lastMessageAt, newest first, by default. Each page holds up to limit conversations
(25 by default, 100 max). When hasMore is true, pass nextToken as searchAfter, with the same filters and sort,
to get the next page. total is only computed on the first page (it is null on the next ones).
List filters (identityIds, leadIds, status…) take comma-separated values, e.g. status=OPEN,SNOOZED.
favourite and read apply to the member behind the API key (by default, the workspace owner).
| Parameter | In | Required | Description |
|---|---|---|---|
q |
query | no | Full-text search on leads (name, company…). Only conversations of the matching leads are returned. |
identityIds |
query | no | Identity IDs, comma-separated. |
leadIds |
query | no | Lead IDs, comma-separated. |
audienceIds |
query | no | Audience IDs, comma-separated. |
campaignIds |
query | no | Campaign IDs, comma-separated. A campaign filters on its audience and its identity; ignored for each of these when audienceIds or identityIds is also set. |
status |
query | no | Conversation status, comma-separated. |
lastMessageStatus |
query | no | Status of the last message, comma-separated. RECEIVED = the lead spoke last, SENT = you spoke last. |
lastMessageType |
query | no | Channel of the last message, comma-separated. EMAIL and LINKEDIN are the useful values. |
lastMessageAtFrom |
query | no | Last message at or after this date (Unix timestamp in milliseconds). |
lastMessageAtTo |
query | no | Last message at or before this date (Unix timestamp in milliseconds). |
callCompletedAtFrom |
query | no | Call task completed at or after this date (Unix timestamp in milliseconds). |
callCompletedAtTo |
query | no | Call task completed at or before this date (Unix timestamp in milliseconds). |
leadReplied |
query | no | Only conversations where the lead has replied (true) or not (false). |
unsubscribed |
query | no | Filter on unsubscribed leads. |
favourite |
query | no | true: only the conversations starred by the member. false has no effect. |
read |
query | no | false: unread conversations only; true: read ones only. |
perseverance |
query | no | Reply manager follow-up filter. PERSEVERED = conversations already followed up; TO_PERSEVERE = open or curious replies still to follow up. |
limit |
query | no | Page size. |
searchAfter |
query | no | Pagination cursor, the nextToken of the previous page. Not a conversation ID. |
sortField |
query | no | Sort field. |
sortDirection |
query | no | -1 descending (newest first), 1 ascending. |
Response 200:
{ "statusCode": 200, "data": [], "total": 0, "hasMore": false, "nextToken": null}Errors: 400, 401, 403, 429, 500.
GET /conversations/{conversationId}/messages
Section titled “GET /conversations/{conversationId}/messages”Get Conversation Messages
Returns the 25 most recent messages of a conversation, sorted chronologically (oldest first).
Besides LinkedIn and email messages, the list can hold inbox system entries (channel LGM, COMMENT,
MANUAL_TASK…). For these, and for messages with status INFO or SEND_FAILED, direction is received:
rely on status to know who wrote a message.
| Parameter | In | Required | Description |
|---|---|---|---|
conversationId |
path | yes | The conversation ID (24-char hex ObjectId). |
page |
query | no | Accepted (integer, 0 or more) but currently has no effect: the 25 most recent messages are always returned. |
Response 200:
{ "statusCode": 200, "data": [ { "id": "6650a1b2c3d4e5f6a7b8c951", "channel": "LINKEDIN", "status": "SENT", "content": "Hi John, I noticed your work at Acme and wanted to connect.", "sender": "john-doe-acme", "createdAt": "2026-03-18T09:00:00.000Z", "direction": "sent", "attachments": [], "isUnsafe": false, "sentByReplyManager": false }, { "id": "6650a1b2c3d4e5f6a7b8c952", "channel": "LINKEDIN", "status": "RECEIVED", "content": "Thanks for reaching out, I'd love to discuss further!", "sender": "john-doe-acme", "createdAt": "2026-03-20T16:45:00.000Z", "direction": "received", "attachments": [], "isUnsafe": false, "sentByReplyManager": false } ], "total": 2}Errors: 400, 401, 403, 404, 429, 500.
Manual tasks
Section titled “Manual tasks”POST /manualtasks/call/complete
Section titled “POST /manualtasks/call/complete”Complete Call Manual Task
Completes a call manual task of a campaign, as when you log the call outcome in the inbox.
The task is marked as done and leaves the call inbox. The outcome (callStatus, and tag if given) is then applied
to the lead asynchronously.
Request body:
{ "manualTaskId": "6650a1b2c3d4e5f6a7b8c960", "callStatus": "DECISIVE", "tag": "CALL_BOOKED"}Response 200:
{ "manualTaskId": "6650a1b2c3d4e5f6a7b8c960", "callStatus": "DECISIVE", "tag": "CALL_BOOKED", "status": "completed"}Errors: 400, 401, 403, 404, 429, 500.
POST /manualtasks/message/complete
Section titled “POST /manualtasks/message/complete”Complete Message Manual Task
Completes a custom message manual task: you provide the message that the campaign step will send to the lead.
The message is stored on the pending campaign action, which is then released and sent by the campaign. The channel comes from the campaign step:
- LinkedIn:
messageis the text sent.subject,cc,bccandattachmentsare ignored. - Email:
messageis the HTML body,subjectthe subject;cc,bccandattachmentsare optional.
Other custom message tasks of the same lead for the same campaign step are removed.
Request body:
{ "manualTaskId": "6650a1b2c3d4e5f6a7b8c961", "message": "Hi John, congrats on the new role at Acme!"}Response 200:
{ "manualTaskId": "6650a1b2c3d4e5f6a7b8c961", "status": "completed"}Errors: 400, 401, 403, 404, 429, 500.
Website visitor
Section titled “Website visitor”POST /leads/visitors/{provider}/{audienceId}
Section titled “POST /leads/visitors/{provider}/{audienceId}”Website Visitor Native Webhook
Native webhook for website visitor identification tools (RB2B, Warmly, Vector). Copy this URL into the tool’s dashboard (for RB2B: Integration > La Growth Machine):
https://apiv2.lagrowthmachine.com/flow/leads/visitors/{provider}/{audience}?apikey=YOUR_API_KEYEach visitor identified by the tool is created (or updated) as a lead and added to the audience.
Make sure to replace YOUR_API_KEY with your actual API key. The key is in the URL because these tools only accept a
URL; the Authorization and x-api-key headers also work.
The audience
Section titled “The audience”The last segment is the name of the audience (or its ID). If no audience has this name or ID, an audience with
this name is created. Defaults used in the tools’ setup: RB2B_Website_Visitor, Warmly_Website_Visitors,
Vector_Website_Visitors. Spaces are allowed, URL-encoded: High%20Intent%20Visitors.
Mapping to lead fields
Section titled “Mapping to lead fields”The body is the tool’s own payload. Fields are mapped as below; other fields are ignored. The visitor must have at least a LinkedIn URL or an email, or a first name, last name and company (name or website).
RB2B
{ "LinkedIn URL": "linkedinUrl", "First Name": "firstname", "Last Name": "lastname", "Title": "jobTitle", "Company Name": "companyName", "Business Email": "proEmail", "Website": "companyUrl", "Industry": "industry", "Employee Count": "customAttribute1", "Estimate Revenue": "customAttribute2", "City": "location", "State": "customAttribute3", "Zipcode": "customAttribute4", "Seen At": "customAttribute5", "Referrer": "customAttribute6", "Captured URL": "customAttribute7", "Tags": "customAttribute8"}Warmly
{ "LinkedIn URL": "linkedinUrl", "Business Email": "proEmail", "First Name": "firstname", "Last Name": "lastname", "Title": "jobTitle", "City": "location", "State": "customAttribute3", "Country": "customAttribute4", "Company Name": "companyName", "Seen At": "customAttribute5", "Website": "companyUrl", "Industry": "industry", "Employee Count": "customAttribute1", "Estimate Revenue": "customAttribute2", "Referrer": "customAttribute6", "Session Identification Confidence": "customAttribute8", "Pages Viewed": "customAttribute7"}Vector
{ "contact.first_name": "firstname", "contact.last_name": "lastname", "contact.email": "proEmail", "contact.title": "jobTitle", "contact.company": "companyName", "contact.linkedin_url": "linkedinUrl", "segment.id": "customAttribute1", "segment.name": "customAttribute2"}All values are stored as strings.
| Parameter | In | Required | Description |
|---|---|---|---|
provider |
path | yes | The visitor identification tool. |
audienceId |
path | yes | Name of the audience (matched first) or its ID. Created with this name if it does not exist. URL-encode spaces. |
Request body:
{ "LinkedIn URL": "https://www.linkedin.com/in/jane-smith-acme", "First Name": "Jane", "Last Name": "Smith", "Title": "CTO", "Company Name": "Acme", "Business Email": "jane.smith@acme.com", "Website": "https://acme.com"}Response 200:
{ "leadId": "64b7f0c2a1d3000000000007", "status": "created", "isNewLead": true}Errors: 400, 401, 404, 429.
Identities
Section titled “Identities”GET /identities
Section titled “GET /identities”List identities
Lists the identities (the LinkedIn and email accounts that send your campaigns) connected to the workspace.
Use an identity id wherever an identityId is required, for example to create an audience from a LinkedIn
URL or to send a message.
Response 200:
{ "statusCode": 200, "identities": [ { "id": "64b7f0c2a1d3000000000004", "name": "John Doe", "linkedinConnected": false, "companyName": null, "companyUrl": null, "location": null, "jobTitle": null } ]}Errors: 401, 429, 500.
GET /identities/{identityId}/preferences
Section titled “GET /identities/{identityId}/preferences”List identity preferences
Lists the preferences saved for an identity, sorted by category then key (by key only when filtered on a category). Preferences are key-value pairs, grouped by category, used to personalize the AI-generated content of this identity (tone, language, signature…).
| Parameter | In | Required | Description |
|---|---|---|---|
identityId |
path | yes | ID of the identity (24-character hex string). |
category |
query | no | Only return the preferences of this category. |
Response 200:
{ "statusCode": 200, "data": [ { "id": "64b7f0c2a1d300000000000e", "category": "tone", "key": "formality", "value": "Casual, first name basis, no jargon.", "channel": null, "createdAt": "2026-10-05T09:36:29.395Z", "updatedAt": "2026-10-05T09:36:29.395Z" } ], "total": 1}Errors: 400, 401, 404, 429, 500.
POST /identities/{identityId}/preferences
Section titled “POST /identities/{identityId}/preferences”Save an identity preference
Creates or updates a preference of an identity. A preference is identified by its category, key and
channel: saving the same triple again replaces its value. An identity holds at most 50 preferences.
The response does not return the preference ID; list the preferences to get it.
| Parameter | In | Required | Description |
|---|---|---|---|
identityId |
path | yes | ID of the identity (24-character hex string). |
Request body:
{ "category": "tone", "key": "formality", "value": "Casual, first name basis, no jargon."}Response 200:
{ "statusCode": 200, "data": { "success": true }}Errors: 400, 401, 404, 429, 500.
DELETE /identities/{identityId}/preferences/{preferenceId}
Section titled “DELETE /identities/{identityId}/preferences/{preferenceId}”Delete an identity preference
Deletes one preference of an identity. Get the preferenceId from GET /identities/{identityId}/preferences.
| Parameter | In | Required | Description |
|---|---|---|---|
identityId |
path | yes | ID of the identity (24-character hex string). |
preferenceId |
path | yes | ID of the preference to delete (24-character hex string). |
Response 200:
{ "statusCode": 200, "data": { "success": true }}Errors: 400, 401, 404, 429, 500.
Members
Section titled “Members”GET /me
Section titled “GET /me”Get the authenticated member
Returns the member (the person) behind the credentials used for the call: their member ID and email. This is the person, not the workspace: in a workspace shared by several members, each member gets their own answer. With a standard API key, the member is the owner of the workspace.
Response 200:
{ "id": "64b7f0c2a1d3000000000001", "email": "john.doe@acme.com"}Errors: 401, 429, 500.
GET /members
Section titled “GET /members”List members
Lists the members (users) of the workspace. Use a member id as the memberId required by some endpoints,
such as sending a LinkedIn message.
Response 200:
{ "statusCode": 200, "members": [ { "id": "64b7f0c2a1d3000000000001", "name": "John Doe", "label": "John Doe" } ]}Errors: 401, 429, 500.
Workspace
Section titled “Workspace”GET /workspaces
Section titled “GET /workspaces”List workspaces
Lists the workspaces the authenticated person can act in: their own workspace and the sub-workspaces
(clones) of the same account. Most accounts have a single workspace, in which case multiWorkspace is
false and nothing else is needed.
Acting in another workspace
Section titled “Acting in another workspace”By default every call acts in the workspace of your API key. On a multi-workspace account, add the header
X-LGM-Workspace: <workspaceId> (a workspaceId from this list) to any endpoint of the API to act in
that workspace instead. The workspace must belong to the same account as your key; otherwise the call is
rejected with a 403 (You are not a member of the targeted workspace). On this endpoint, isCurrent
marks the workspace the call acted in (the one from the header, or the default one).
| Parameter | In | Required | Description |
|---|---|---|---|
X-LGM-Workspace |
header | no | ID of the workspace to act in (see above). Accepted by every endpoint of the API. |
Response 200:
{ "multiWorkspace": false, "workspaces": [ { "workspaceId": "64b7f0c2a1d3000000000001", "name": "Acme", "companyName": "Acme", "slug": "", "plan": "PRO", "identitiesCount": 1, "isCurrent": true } ]}Errors: 401, 403, 429, 500.
Credits
Section titled “Credits”GET /credits
Section titled “GET /credits”Get Credits
Returns the credit balance for the authenticated workspace, and whether it has CRM and extension access.
credits.total— total credits available.credits.perishable— credits that expire soon (already included intotal).
Credits are spent by POST /flow/leads/enrich (up to 1, 5 or 20 credits depending on enrichType) and POST /flow/posts/enrich (1 credit).
Response 200:
{ "statusCode": 200, "credits": { "total": 1500, "perishable": 1500 }, "permissions": { "crmAccess": false, "extensionAccess": false }}Errors: 400, 401, 429.
POST /crm/search
Section titled “POST /crm/search”Search CRM
Looks up a contact in the CRM connected to the workspace (HubSpot or Pipedrive).
Body — at least one of the following is required
Section titled “Body — at least one of the following is required”email|proEmail|persoEmail(email)- OR
firstname(+lastnameto narrow down) - OR
hubspotId/pipedriveId(direct lookup by CRM contact id)
Additional optional fields
Section titled “Additional optional fields”lastname,companyName(string, max 255)linkedinUrl(string, max 500),linkedinLongPublicId— used when the LinkedIn URL is mapped to a CRM field.
Matching order
Section titled “Matching order”- CRM contact id.
- LinkedIn URL, when mapped to a CRM field.
- Emails.
firstname+lastname.
Returns one entry per CRM connected to the workspace (empty array when none). When a search matches several contacts, the entry has found: false and tooManyResults: true: refine the search. A matched contact comes with its deals, lifecycle stage, owner and recent activities.
Request body:
{ "proEmail": "john.doe@acme.com"}Response 200:
{ "statusCode": 200, "results": []}Errors: 400, 401, 429.