Skip to main content
Glama

Ingest Prospects + Sequences (per account)

li_ingest_prospects
Destructive

Push prospects WITH their full prefilled sequence into our DB for a chosen sending account, deduped on profileUrl (re-ingesting updates rather than duplicates). campaignName SELECTS the campaign: a name that already exists on that account is added to, and a name that does not is created. The reply always says which one you got — result.campaignName, result.campaignLaunchedAt and result.campaignCreated — because a batch landing in a campaign you did not mean is otherwise invisible. Read result.warnings: the sharpest one says these prospects joined a campaign that is ALREADY LIVE, which means launch:false staged nothing. Each prospect carries connectionNote + firstMessage + followUp1..3/closer and optional metadata. startStage="connection" (default) enters the invite drip; startStage="message" (already a 1st-degree connection, e.g. an inbound invitation you accepted) NEVER sends an invite — and REQUIRES that the connection already exists. Nothing verifies it: if you are not 1st-degree when the first message comes due, the profile has no Message control, the send fails as "no message entry point", and that failure is PERMANENT — the prospect is retired having received nothing. A PENDING inbound invitation is NOT a connection, so accept it (li_accept_invitations, and check the reply says it was accepted) BEFORE staging anyone at the message stage — their first message is scheduled for connectionWaitHours (24h by default) after the campaign is launched, and with launch:false nothing is scheduled until you call li_launch_campaign. Someone already queued, invited or connected through this account's invite-only list (li_send_connection_now) is not invited twice: their invite is MOVED into this campaign with its state, the sequence attached, listed in result.adoptedFromInviteOnly. Replaces the skill's Airtable master sheet. Does not generate text — supply it.

Every confirmed ingest also returns collisions: who among these prospects had ALREADY been approached from the accounts you can see, measured just before the rows were written. A collision is NOT an error and NOTHING was refused — every prospect was imported. It is there because the cost lands later and invisibly: re-importing someone into the SAME campaign is idempotent, but importing them into a DIFFERENT campaign creates a second invite the drip attempts, and at send time LinkedIn answering "already invited" or "already connected" sends nothing while still spending a ~50s browser session the account then has to wait behind. Anything else sends a REAL second invite, charged against each account's rolling 7-day invite allowance this system enforces, set by the account's detected LinkedIn tier — 63-70 a week (5-10 a day) new or unverified, 90-100 a week (18-24 a day) established, 135-150 a week (25-30 a day) Premium, redrawn within the range each week. Our own policy, not a published LinkedIn limit; the daily band exists only to stop a burst. li_list_accounts gives each account's actual numbers. collisions.summary is one sentence safe to relay verbatim (it is folded into result.plan too), and the report is bounded by the accounts you can see and reports nothing about anything outside them (collisions.scopeNote says so), so a clean result means 'not in your pipeline', not 'nobody anywhere'. Do NOT call li_check_targets first to get it: this runs the same check, and it runs it knowing the campaign, so people already in that campaign read as an idempotent update here instead of as a clash elsewhere. That tool is for the list you have not decided to import yet.

ONE CALL, MANY CAMPAIGNS (up to 500 prospects): each prospect may name its own account (sessionLabel/sessionId), campaign (campaignId, or campaignName to find-or-create), strategy and ICP; a field it leaves out is inherited from the call. The batch is grouped by (account, campaign, strategy, ICP), each group is its own import with its own pacing sentence and collision report, and the reply's campaigns lists every campaign touched (created or existing, imported/updated/rejected) while routed gives each submitted row's campaign and outcome by position. A row naming a campaignId takes that campaign's account, strategy and ICP. suppressed and rejectedRows are lifted to the top level as one list. Everything is resolved before anything is written: an unknown account, campaign, strategy or ICP, or the same person routed to TWO places, refuses the whole call. launch:false stages every campaign it touches.

SHARED COPY: defaultSteps gives one text per field (connectionNote, firstMessage, followUp1..3, closer — the names a row's own copy uses) for every row that brings none. {first} is the only variable: the row's firstName, else the first word of name, filled in at import, so the stored text is what is sent. A row with no first name for a template using it is not imported and says why; copy reports per-step lengths and every row over a limit (a note over 300 is shortened, a message over the limit is refused).

The do-not-contact list is the one thing here that DOES refuse: a prospect on it (the team's own list or the install-wide one) is NOT imported, and comes back by name with the reason in result.suppressed (top-level suppressed when the batch was split). Relay that list — a person the user asked to reach who silently never entered the pipeline looks like a bug weeks later. Manage the list with li_suppress_profiles / li_list_suppressed / li_unsuppress_profile.

Engage-before-invite: a connection-stage prospect may carry engagePostUrl (one of their own posts, the one the note and first message mention). The sending account visits their profile and reacts to it first, and the invite follows about a day later, inside the window it goes out in (the person's own morning when the account times invites to each prospect, otherwise its sending hours; 18-30 hours with no window to aim at); about one in seven is only viewed. Reactions are capped at 15-25 a day per account (our policy, not LinkedIn's) and run in sending hours, so a large batch is engaged over days while everyone else keeps being invited. A post that is gone or not theirs releases the invite without a reaction. Message-stage prospects ignore engagePostUrl (result.warnings says so). result.engagement counts both, and the campaign contacts read shows each person's engagement.

Input Schema

NameRequiredDescriptionDefault
icpIdNoICP label id from li_list_icps (or use icpName); optional
launchNoDefault true — ingest immediately starts the drip (invites/first messages). Set false to STAGE the prospects for review WITHOUT sending anything; call li_launch_campaign later to start.
icpNameNoICP label by name, from li_list_icps; optional
prospectsYes1-500 prospects, each with profileUrl and the prefilled text for every strategy step (plus connectionNote for invites), or that text from defaultSteps. Each may name its own account, campaign, strategy and ICP.
sessionIdNoSending account id (or use sessionLabel)
campaignIdNoImport into THIS campaign, by id from li_list_campaigns. Authoritative: it names exactly one campaign, where a name can match two. Refused if it is not on the sending account or is completed — never quietly swapped for another campaign. Use it whenever you mean an existing campaign; use campaignName to make a new one.
strategyIdNoSequence template id from li_list_strategies (or use strategyName)
campaignNameNoSelects a campaign BY NAME on the sending account: an existing active campaign of that name is added to, and a name that does not exist yet is created. Stored and matched EXACTLY as you give it — nothing is appended, so calling again with the same name reaches the same campaign. Omit for the account's default managed campaign. Prefer campaignId when you mean a campaign that already exists. Two accounts may hold a campaign of the same name without clashing: the match is per account, and each campaign reports its own `accountLabel`.
defaultStepsNoShared copy: text per step (connectionNote, firstMessage, followUp1, followUp2, followUp3, closer) for every row that brings none of its own. {first} is the row's first name (firstName, else the first word of name), filled in at import; no other variable exists. A row with no first name for a template that uses {first} is not imported, and says why.
sessionLabelNoSending account label, e.g. "Deven" (defaults to the default account)
strategyNameNoSequence template; omit to use/create the default Day 1/3/7/14
confirmIngestYesMust be true to write; false returns a dry-run preview
idempotencyKeyNoAny unique string for THIS request (e.g. a UUID). Pass the same key when retrying after a timeout, 502 or 503: a call that already finished returns its stored answer (24h) instead of importing twice. Ignored on a dry run.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / prospects / items / properties / engagePostUrl / description
      Previous value: -"Engage-before-invite: ONE of this person's own recent posts (https://www.linkedin.com/feed/update/urn:li:activity:<id>/ or a /posts/ link), the one the note and first message mention. The sending account visits their profile and reacts to it a day or more BEFORE the invite goes out (about one in seven is only viewed), and the invite then waits 20-60 hours. Connection stage only: ignored for message-stage prospects. A link that is not a LinkedIn post refuses the row. If the post is gone or turns out not to be theirs, nothing is clicked and the invite goes ahead without it."New value: +"Engage-before-invite: ONE of this person's own recent posts (https://www.linkedin.com/feed/update/urn:li:activity:<id>/ or a /posts/ link), the one the note and first message mention. The sending account visits their profile and reacts to it first (about one in seven is only viewed). The invite follows about a day later, never sooner than 16 hours, inside the window it goes out in: the person's own morning invite window when the account times invites to each prospect, otherwise the account's sending hours (18-30 hours when there is no window to aim at, such as sending hours switched off). Connection stage only: ignored for message-stage prospects. A link that is not a LinkedIn post refuses the row. If the post is gone or turns out not to be theirs, nothing is clicked and the invite goes ahead without it."
  2. Changed1 schema field changed
    • changedInput schema / properties / prospects / items / properties / profileUrl / description
      Previous value: -"Canonical LinkedIn profile URL"New value: +"LinkedIn profile URL. A search id from li_search_leads (/in/ACwAA…) is accepted as is and resolved to the public profile URL before anything is written (reported as `searchIds`)."
  3. Changed11 schema fields changed
    • addedInput schema / properties / defaultSteps
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "Shared copy: text per step (connectionNote, firstMessage, followUp1, followUp2, followUp3, closer) for every row that brings none of its own. {first} is the row's first name (firstName, else the first word of name), filled in at import; no other variable exists. A row with no first name for a template that uses {first} is not imported, and says why.",
      +  "properties": {
      +    "closer": {
      +      "description": "Default text for the \"closer\" step",
      +      "maxLength": 8000,
      +      "type": "string"
      +    },
      +    "connectionNote": {
      +      "description": "Default invite note (connection-stage rows only)",
      +      "maxLength": 8000,
      +      "type": "string"
      +    },
      +    "firstMessage": {
      +      "description": "Default text for the \"firstMessage\" step",
      +      "maxLength": 8000,
      +      "type": "string"
      +    },
      +    "followUp1": {
      +      "description": "Default text for the \"followUp1\" step",
      +      "maxLength": 8000,
      +      "type": "string"
      +    },
      +    "followUp2": {
      +      "description": "Default text for the \"followUp2\" step",
      +      "maxLength": 8000,
      +      "type": "string"
      +    },
      +    "followUp3": {
      +      "description": "Default text for the \"followUp3\" step",
      +      "maxLength": 8000,
      +      "type": "string"
      +    }
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / idempotencyKey
      Added value: +{
      +  "description": "Any unique string for THIS request (e.g. a UUID). Pass the same key when retrying after a timeout, 502 or 503: a call that already finished returns its stored answer (24h) instead of importing twice. Ignored on a dry run.",
      +  "maxLength": 200,
      +  "minLength": 8,
      +  "type": "string"
      +}
    • changedInput schema / properties / prospects / description
      Previous value: -"1-200 prospects, each with profileUrl and the prefilled text for every strategy step (plus connectionNote for invites)"New value: +"1-500 prospects, each with profileUrl and the prefilled text for every strategy step (plus connectionNote for invites), or that text from defaultSteps. Each may name its own account, campaign, strategy and ICP."
    • addedInput schema / properties / prospects / items / properties / campaignId
      Added value: +{
      +  "description": "Land this ONE prospect in this existing campaign (id from li_list_campaigns), overriding the call's. Its account is the campaign's when the row names none; it takes the campaign's own strategy and ICP.",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / campaignName
      Added value: +{
      +  "description": "Land this ONE prospect in the campaign of this name on its account (created if missing), overriding the call's campaign. Stored and matched exactly as given (nothing is appended), so the same name reaches the same campaign.",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / firstName
      Added value: +{
      +  "description": "First name for {first} in defaultSteps; else the first word of name. Not stored.",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / icpId
      Added value: +{
      +  "description": "ICP label for this prospect's campaign, overriding the call's (or icpName)",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / icpName
      Added value: +{
      +  "description": "ICP label by name, for this prospect only",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / strategyId
      Added value: +{
      +  "description": "Strategy for this prospect's campaign, overriding the call's (or strategyName)",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / strategyName
      Added value: +{
      +  "description": "Strategy by name, for this prospect only",
      +  "type": "string"
      +}
    • changedInput schema / properties / prospects / maxItems
      Previous value: -200New value: +500
  4. Changed2 schema fields changed
    • addedInput schema / properties / prospects / items / properties / engagePostUrl
      Added value: +{
      +  "description": "Engage-before-invite: ONE of this person's own recent posts (https://www.linkedin.com/feed/update/urn:li:activity:<id>/ or a /posts/ link), the one the note and first message mention. The sending account visits their profile and reacts to it a day or more BEFORE the invite goes out (about one in seven is only viewed), and the invite then waits 20-60 hours. Connection stage only: ignored for message-stage prospects. A link that is not a LinkedIn post refuses the row. If the post is gone or turns out not to be theirs, nothing is clicked and the invite goes ahead without it.",
      +  "type": "string"
      +}
    • addedInput schema / properties / prospects / items / properties / engageReaction
      Added value: +{
      +  "description": "Which reaction to leave on engagePostUrl: LIKE (default), PRAISE (Celebrate), EMPATHY (Love), INTEREST (Insightful), APPRECIATION (Support) or ENTERTAINMENT (Funny). Needs engagePostUrl.",
      +  "type": "string"
      +}
  5. Changed23 schema fields changed
    • addedInput schema / properties / icpId / description
      Added value: +"ICP label id from li_list_icps (or use icpName); optional"
    • addedInput schema / properties / icpName / description
      Added value: +"ICP label by name, from li_list_icps; optional"
    • addedInput schema / properties / prospects / description
      Added value: +"1-200 prospects, each with profileUrl and the prefilled text for every strategy step (plus connectionNote for invites)"
    • addedInput schema / properties / prospects / items / properties / closer / description
      Added value: +"Text for the \"closer\" step"
    • addedInput schema / properties / prospects / items / properties / collaborationAngle / description
      Added value: +"The angle the sequence leads with"
    • addedInput schema / properties / prospects / items / properties / company / description
      Added value: +"Current employer"
    • addedInput schema / properties / prospects / items / properties / connectionNote / description
      Added value: +"Note sent WITH the invite (connection stage only; free accounts cannot deliver notes)"
    • addedInput schema / properties / prospects / items / properties / context / description
      Added value: +"Free-text notes on how you found them or why now"
    • addedInput schema / properties / prospects / items / properties / firstMessage / description
      Added value: +"Text for the \"first\" step, sent connectionWaitHours after acceptance"
    • addedInput schema / properties / prospects / items / properties / fitQuality / description
      Added value: +"Your fit grade, e.g. strong / medium / weak"
    • addedInput schema / properties / prospects / items / properties / followUp1 / description
      Added value: +"Text for the \"followup_1\" step"
    • addedInput schema / properties / prospects / items / properties / followUp2 / description
      Added value: +"Text for the \"followup_2\" step"
    • addedInput schema / properties / prospects / items / properties / followUp3 / description
      Added value: +"Text for the \"followup_3\" step"
    • addedInput schema / properties / prospects / items / properties / headline / description
      Added value: +"LinkedIn headline"
    • addedInput schema / properties / prospects / items / properties / icpBucket / description
      Added value: +"Your ICP bucket for them (free text)"
    • addedInput schema / properties / prospects / items / properties / location / description
      Added value: +"Location as shown on the profile"
    • addedInput schema / properties / prospects / items / properties / name / description
      Added value: +"Full name"
    • addedInput schema / properties / prospects / items / properties / productUrl / description
      Added value: +"Their product or company URL"
    • addedInput schema / properties / prospects / items / properties / role / description
      Added value: +"Current job title"
    • addedInput schema / properties / prospects / items / properties / timezone / description
      Added value: +"IANA timezone, e.g. Asia/Kolkata; used by prospect-local sending windows"
    • addedInput schema / properties / prospects / items / properties / whatTheySell / description
      Added value: +"What their company sells, in a line"
    • addedInput schema / properties / prospects / items / properties / whyTheyFit / description
      Added value: +"Why they fit, in a line"
    • addedInput schema / properties / strategyId / description
      Added value: +"Sequence template id from li_list_strategies (or use strategyName)"
  6. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare a destructive, open-world write, and the description goes far beyond them: dedup on profileUrl, idempotency, permanent failure of message-stage sends when not 1st-degree, the exact semantics of launch:false staging, the ~50s browser session cost of a duplicate invite, the rolling 7-day invite allowance and its daily bands, and the do-not-contact refusal. This is exceptional disclosure for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core operation and its selection semantics are front-loaded, and the sectioned structure (ONE CALL MANY CAMPAIGNS, SHARED COPY, engage-before-invite) is navigable. The length is largely justified by the tool's genuine complexity, but several passages (invite-allowance tiers, engagement timing details) are more expansive than an agent needs for correct invocation.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 13-parameter nested tool with 500-item batches, no output schema, and destructive behavior, the description covers the result contract explicitly (result.campaignName, result.campaignCreated, result.warnings, result.collisions, result.routed, result.suppressed, result.adoptedFromInviteOnly), including what each return field means. An agent has everything needed to invoke and interpret the call.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3, but the description adds cross-parameter semantics the schema alone does not convey: campaignName's find-or-create exact-match behavior versus campaignId's authoritative one-campaign semantics, startStage's divergent invite/message behavior, and engagePostUrl being ignored at message stage. Some prose overlaps the schema (e.g. {first} substitution, engagement caps) rather than adding new per-parameter meaning, keeping it at 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening sentence states a specific verb and resource with scope: 'Push prospects WITH their full prefilled sequence into our DB for a chosen sending account, deduped on profileUrl.' It further distinguishes itself from siblings by naming li_check_targets, li_send_connection_now, li_accept_invitations and li_launch_campaign in the context of when this tool does and does not apply.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use guidance is present throughout: message-stage requires an existing 1st-degree connection and directs the agent to accept pending invitations via li_accept_invitations first; it explicitly says NOT to call li_check_targets first; and it routes suppression management to li_suppress_profiles/li_list_suppressed. Alternatives and preconditions are named concretely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources