Skip to documentation

Documentation

Leadverse REST API

Connect your scripts and apps to campaigns, leads, Reddit outreach, and insights. 32 endpoints use your existing account permissions and plan limits.

OpenAPI 3.1 specification

Create an API key

Open Settings → Integrations → API keys. Choose a name, permissions, and expiry: 30, 90, or 365 days, or Never. Copy the key when it appears: it is shown only once. An active plan with API access is required. You can have up to 10 active keys, including keys that never expire.

Read-only keys can use GET endpoints. Read-and-write keys can also change data and send messages. Keys inherit the owner’s existing team access. Revoke a key in Settings to stop new requests; work already accepted may finish.

Make your first request

bash
curl --fail-with-body 'https://api.leadverse.ai/v1/account/usage' \
  -H "Authorization: Bearer $LEADVERSE_API_KEY"

Successful reads return a JSON object with a data field containing the endpoint result. Lists use limit and offset where listed below, with a maximum limit of 50. Booleans are true or false; arrays can repeat a query parameter or use comma-separated values. Unknown parameters are rejected.

bash
curl --fail-with-body 'https://api.leadverse.ai/v1/leads?limit=20&offset=0&status=new&platforms=reddit' \
  -H "Authorization: Bearer $LEADVERSE_API_KEY"

Writes and retries

Every POST, PATCH, and DELETE requires an Idempotency-Key. Generate a UUID once per intended action and store it with the request. If a request times out or returns 503, resend the same body and key. Never generate a fresh key just to retry.

bash
curl --fail-with-body -X PATCH 'https://api.leadverse.ai/v1/leads/status' \
  -H "Authorization: Bearer $LEADVERSE_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 19e4c4ae-9c0c-4cb1-9504-845a9f992655' \
  --data '{"lead_ids":[123],"status":"contacted"}'

Replace 123 with an owned lead ID and the example Idempotency-Key with a UUID for your intended action.

202 Accepted (example)
json
{
  "operation": {
    "id": "8c8866d6-ae27-412d-b6bf-a9499811c2ec",
    "status": "running",
    "url": "/v1/operations/8c8866d6-ae27-412d-b6bf-a9499811c2ec",
    "result": null,
    "error": null
  }
}

Poll the returned URL with the same API key, waiting at least five seconds between polls. The status is running, succeeded, failed, or unknown. Succeeded includes the tool result, which may describe a queued job: for example, a campaign sync continues in the background and DMs follow delivery pacing. Inspect the result for per-item outcomes.

bash
curl --fail-with-body 'https://api.leadverse.ai/v1/operations/8c8866d6-ae27-412d-b6bf-a9499811c2ec' \
  -H "Authorization: Bearer $LEADVERSE_API_KEY"

Sending a write request authorizes its effects, including messages or deletion. Leadverse does not post public comments through the API; comment drafts are suggestions. Connect Reddit in the Leadverse app before sending DMs. Loading the targeting editor is a POST because it can refresh stored targeting.

Limits and errors

API requests are limited to 60 per minute and 2,000 per UTC day per account, shared across all keys. Polling and retrying also count. These are separate from MCP transport limits; existing campaign, AI, and DM quotas still apply. The API may return 503 while write capacity is occupied.

json
{
  "error": {
    "code": "invalid_key",
    "message": "The API key is invalid, expired, or revoked."
  }
}
HTTPCodeNext step
400invalid_json / idempotency_key_requiredCheck the JSON body and Idempotency-Key header.
401invalid_keySupply an active, unexpired API key.
403insufficient_scope / not_entitled / forbiddenCheck key permissions, resource access, and your subscription.
404not_foundCheck the route or operation ID. Operations belong to the key that created them.
405method_not_allowedUse the method shown in the endpoint reference.
409idempotency_conflictUse the original request with this Idempotency-Key.
413 / 415body_too_large / invalid_content_typeKeep JSON bodies under 128 KiB and set Content-Type: application/json.
422invalid_parameters / request_refusedCheck parameters or the explanation in error.message.
429rate_limitedWait for the number of seconds in Retry-After.
503api_busy / service_unavailable / api_unavailableRetry after Retry-After. For writes, reuse the same Idempotency-Key.

Endpoint reference

Path parameters go in the URL. GET parameters go in the query string. All other parameters go in a JSON body.

Account

GET/v1/account/usageAccount usage · Read scope

Current plan, remaining daily DM capacity, AI reply allowance, campaign and lead counts, and whether a Reddit account is connected. Call this before queueing DMs so you know the real remaining capacity instead of guessing.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

Campaigns

GET/v1/campaignsList campaigns · Read scope

List the campaigns on this account with their lead counts, platforms and latest sync timestamps. Use leadverse_campaign_syncs_list for live sync progress and results.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}
GET/v1/campaigns/{campaign_id}Get campaign · Read scope

Full detail for one campaign: description, intent, website, engagement filters, negative keywords, blocked users and subreddits, tracked competitors, AI reply settings for both comments and DMs, and the voice-training samples. Does not return search keywords or intent phrases - those are background automation and are not user-editable.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to read."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
POST/v1/campaignsCreate campaign · Read and write key · Asynchronous

Create a campaign. Leadverse generates the targeting model before inserting the campaign, so this call can take one or two minutes. After insertion, the initial lead sync starts automatically in the background. Counts against the plan's campaign limits. Keywords and intent phrases are generated automatically - do not ask the user for them. Any _internal_sync_reference in the result is for tool coordination only; never mention it to the user.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300,
      "description": "What the product or service does, in the user's own words."
    },
    "intent": {
      "type": "string",
      "enum": [
        "People asking for a product like mine",
        "People seeking services I offer"
      ]
    },
    "website": {
      "type": "string",
      "maxLength": 500
    },
    "intents": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "tool_request",
          "alternative_switch",
          "comparison_evaluation",
          "hiring_outsourcing",
          "troubleshooting_fix",
          "pain_frustration",
          "discussion"
        ]
      },
      "maxItems": 7,
      "uniqueItems": true,
      "description": "Which buying intents to collect. Defaults to all when omitted."
    },
    "tracked_competitors": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 500
      },
      "maxItems": 10,
      "description": "Competitor websites to track."
    },
    "min_likes_filter": {
      "type": "integer",
      "minimum": 0
    },
    "min_comments_filter": {
      "type": "integer",
      "minimum": 0
    },
    "max_post_age_days_filter": {
      "type": "integer",
      "minimum": 1
    },
    "negative_keywords": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "example_responses": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 2000
      },
      "maxItems": 10,
      "description": "Voice-training samples - real replies written by the user, used to match their tone."
    }
  },
  "required": [
    "name",
    "description",
    "intent"
  ],
  "additionalProperties": false
}
PATCH/v1/campaigns/{campaign_id}Update campaign · Read and write key · Asynchronous

Edit a campaign. Only the fields you pass are changed. Covers everything the campaign editor exposes: basics, engagement filters, negative keywords, voice-training samples, tracked competitors, and the AI reply settings for comments and DMs.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to edit."
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "description": {
      "type": "string",
      "minLength": 1,
      "maxLength": 300
    },
    "website": {
      "type": "string",
      "maxLength": 500
    },
    "intents": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "tool_request",
          "alternative_switch",
          "comparison_evaluation",
          "hiring_outsourcing",
          "troubleshooting_fix",
          "pain_frustration",
          "discussion"
        ]
      },
      "maxItems": 7,
      "uniqueItems": true
    },
    "tracked_competitors": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 500
      },
      "maxItems": 10
    },
    "min_likes_filter": {
      "type": "integer",
      "minimum": 0
    },
    "min_comments_filter": {
      "type": "integer",
      "minimum": 0
    },
    "max_post_age_days_filter": {
      "type": "integer",
      "minimum": 1
    },
    "negative_keywords": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "example_responses": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 2000
      },
      "maxItems": 10
    },
    "use_same_settings_for_comments_and_dms": {
      "type": "boolean"
    },
    "reply_settings": {
      "type": "object",
      "description": "AI reply settings used for comment suggestions.",
      "properties": {
        "use_custom_prompt": {
          "type": "boolean"
        },
        "custom_prompt_instructions": {
          "type": "string",
          "maxLength": 2000
        },
        "reply_tone": {
          "type": "string",
          "maxLength": 100
        },
        "reply_length": {
          "type": "string",
          "maxLength": 100
        },
        "reply_pitch_level": {
          "type": "string",
          "maxLength": 100
        },
        "reply_call_to_action": {
          "type": "boolean"
        },
        "reply_personalization": {
          "type": "boolean"
        },
        "reply_keywords": {
          "type": "string",
          "maxLength": 500
        }
      },
      "additionalProperties": false
    },
    "dm_reply_settings": {
      "type": "object",
      "description": "AI reply settings used for Reddit DMs. Ignored when use_same_settings_for_comments_and_dms is true.",
      "properties": {
        "dm_use_custom_prompt": {
          "type": "boolean"
        },
        "dm_custom_prompt_instructions": {
          "type": "string",
          "maxLength": 2000
        },
        "dm_reply_tone": {
          "type": "string",
          "maxLength": 100
        },
        "dm_reply_length": {
          "type": "string",
          "maxLength": 100
        },
        "dm_reply_pitch_level": {
          "type": "string",
          "maxLength": 100
        },
        "dm_reply_call_to_action": {
          "type": "boolean"
        },
        "dm_reply_personalization": {
          "type": "boolean"
        },
        "dm_reply_keywords": {
          "type": "string",
          "maxLength": 500
        }
      },
      "additionalProperties": false
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
DELETE/v1/campaigns/{campaign_id}Delete campaign · Read and write key · Asynchronous

Permanently delete a campaign and its leads. This cannot be undone - always confirm with the user first, quoting the campaign name.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to delete."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
POST/v1/campaigns/{campaign_id}/syncsStart campaign sync · Read and write key · Asynchronous

Run a manual sync now to look for new leads. Consumes one of the plan's manual syncs for the day. The sync continues in the background after this returns; use leadverse_campaign_syncs_list to check progress, then call leadverse_leads_search after completion. Any _internal_sync_reference in the result is for tool coordination only; never mention it to the user.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to sync."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
GET/v1/campaigns/{campaign_id}/syncsList campaign syncs · Read scope

List a campaign's sync runs using the same history as the Leadverse campaign screen. Use it after campaign creation or a manual sync to check whether processing is still running, completed, failed or skipped and to read progress plus strong and partial match counts. Results are newest first and paginated. Internal sync references are for tool coordination only; never mention them to the user.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign whose sync progress and history should be read."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
PATCH/v1/campaigns/{campaign_id}/blocklistUpdate blocklist · Read and write key · Asynchronous

Block or unblock Reddit users and subreddits for a campaign. Blocked sources stop producing leads. Use this when the user says a subreddit or poster is irrelevant.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to update."
    },
    "block_users": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "unblock_users": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "block_subreddits": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "unblock_subreddits": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 50
    },
    "apply_to_all_campaigns": {
      "type": "boolean",
      "description": "Apply the same change to every campaign on the account, matching the UI checkbox."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
POST/v1/campaigns/{campaign_id}/targeting/loadLoad targeting editor · Read and write key · Asynchronous

Read the editable targeting sections that decide which posts score as leads - the same fields the "Optimize leads" screen shows. Returns the section values plus their maximum lengths. The section names differ between product and service campaigns; always read before writing so you send the right ones. Note: if the campaign is still on an older targeting model, this upgrades it first, which can take a minute.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to read targeting for."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
PATCH/v1/campaigns/{campaign_id}/targetingUpdate targeting · Read and write key · Asynchronous

Rewrite the campaign's targeting sections. Call leadverse_campaign_targeting_get first and send back the same section keys it returned, respecting the returned length limits. This regenerates the scoring model and changes which posts become leads from the next sync onward. It does not rescore existing leads.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to update."
    },
    "sections": {
      "type": "object",
      "description": "Section key/value pairs exactly as returned by leadverse_campaign_targeting_get.",
      "minProperties": 1,
      "additionalProperties": {
        "type": "string",
        "maxLength": 1200
      }
    }
  },
  "required": [
    "campaign_id",
    "sections"
  ],
  "additionalProperties": false
}

Leads

GET/v1/leadsSearch leads · Read scope

Search and filter leads across campaigns by status, buying intent, platform and free text. Returns STRONG matches (AI score 8-10) by default - in Leadverse those are the leads. Scores 6-7 are partial matches: weaker signals that can be useful for product research but are not leads, and are excluded unless you pass match: "partial". Anything below 6 is noise and is never returned. Never describe partial matches or total row counts as lead volume. Returns the post, its AI score and the lead status. Each lead also carries author_dm_contact: when set, this user already sent (or queued) a DM to that lead's Reddit author via another lead, and a second DM would be refused - when picking leads to DM, pass exclude_contacted_authors: true. Defaults to every platform, matching the Leads screen; pass platforms: ["reddit"] to exclude older leads from platforms Leadverse no longer syncs.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_ids": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "description": ""
      },
      "maxItems": 20
    },
    "status": {
      "type": "string",
      "enum": [
        "new",
        "contacted",
        "closed",
        "all"
      ]
    },
    "platforms": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "reddit",
          "x",
          "linkedin",
          "facebook"
        ]
      },
      "maxItems": 4,
      "uniqueItems": true,
      "description": "Restrict to these platforms. Omit for all, as the Leads screen does."
    },
    "categories": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "tool_request",
          "alternative_switch",
          "comparison_evaluation",
          "hiring_outsourcing",
          "troubleshooting_fix",
          "pain_frustration",
          "discussion"
        ]
      },
      "maxItems": 7,
      "uniqueItems": true
    },
    "match": {
      "type": "string",
      "enum": [
        "strong",
        "partial",
        "all"
      ],
      "description": "strong (score 8-10) is the default and is what \"leads\" means. partial (6-7) are weaker signals, useful for product research but not leads. all (1-10) includes noise and should only be used if explicitly asked."
    },
    "search": {
      "type": "string",
      "maxLength": 200
    },
    "exclude_contacted_authors": {
      "type": "boolean",
      "description": "Drop leads whose Reddit author was already DM'd or has a DM queued (author_dm_contact set). Use when selecting leads to DM."
    },
    "sort_by": {
      "type": "string",
      "enum": [
        "ai_score",
        "created_date",
        "post_created_date"
      ]
    },
    "sort_order": {
      "type": "string",
      "enum": [
        "asc",
        "desc"
      ]
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}
GET/v1/leads/{lead_id}Get lead · Read scope

Full detail for one lead: the complete post or comment, its AI score and the reasoning behind it, the subreddit's self-promotion policy, and the history of actions already taken on it.

URL parameters: lead_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "lead_id": {
      "type": "integer",
      "minimum": 1,
      "description": "Numeric Leadverse lead id."
    }
  },
  "required": [
    "lead_id"
  ],
  "additionalProperties": false
}
PATCH/v1/leads/statusUpdate lead status · Read and write key · Asynchronous

Set the status of up to 50 leads at once (new, contacted or closed).

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "lead_ids": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "description": "Numeric Leadverse lead id."
      },
      "minItems": 1,
      "maxItems": 50,
      "uniqueItems": true
    },
    "status": {
      "type": "string",
      "enum": [
        "new",
        "contacted",
        "closed"
      ]
    }
  },
  "required": [
    "lead_ids",
    "status"
  ],
  "additionalProperties": false
}
POST/v1/leads/deleteDelete leads · Read and write key · Asynchronous

Remove leads from the list. By default this hides them the same way the per-lead Delete button does, which also teaches the ranking model they were unwanted. Pass permanent: true only when the user explicitly wants the rows erased. Always confirm before deleting.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "lead_ids": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "description": "Numeric Leadverse lead id."
      },
      "minItems": 1,
      "maxItems": 50,
      "uniqueItems": true
    },
    "permanent": {
      "type": "boolean",
      "description": "Erase the rows instead of hiding them. Irreversible."
    }
  },
  "required": [
    "lead_ids"
  ],
  "additionalProperties": false
}

Outreach

POST/v1/leads/{lead_id}/draftsDraft a reply · Read and write key · Asynchronous

Generate a reply for a lead using the campaign's own AI reply settings, exactly as the Generate button in Leadverse does - its tone, length, pitch, call to action, keywords and voice-training samples, or its custom prompt instructions if the campaign is configured that way. To change how replies read, edit the campaign with leadverse_campaign_update rather than asking for a one-off variation. Consumes one AI reply from the plan's allowance. Returns text only - nothing is sent or posted. Comment drafts are SUGGESTIONS the user posts themselves; Leadverse never posts comments to Reddit. To deliver a DM use leadverse_dm_send or leadverse_dm_queue_add. A first DM is deliberately short, casual and link-free, and often stays general rather than quoting specifics from the post - that is correct, not a weakness. Reddit filters link-heavy messages from cold senders into spam, so the goal of message one is only to get a reply. Do not rewrite the draft to add links, the product name, or more detail unless the user asks. Once they reply, leadverse_conversation_reply is the safe place to send links.

URL parameters: lead_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "lead_id": {
      "type": "integer",
      "minimum": 1,
      "description": "Numeric Leadverse lead id."
    },
    "type": {
      "type": "string",
      "enum": [
        "comment",
        "dm"
      ]
    }
  },
  "required": [
    "lead_id",
    "type"
  ],
  "additionalProperties": false
}
POST/v1/leads/{lead_id}/messagesSend a Reddit DM · Read and write key · Asynchronous

Send a Reddit DM to one lead right now from the connected account, the same as the Send button on a lead in Leadverse. Sending is immediate and cannot be undone - always show the user the exact message and get confirmation first. Daily DM caps and account limits are enforced; if one is hit the send is refused and nothing is delivered. Each Reddit author can be DM'd only once across all leads: if the lead's author_dm_contact field is set the send will be refused, so pick leads without it (leadverse_leads_search with exclude_contacted_authors: true). For more than a few leads use leadverse_dm_queue_add instead, which paces delivery.

URL parameters: lead_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "lead_id": {
      "type": "integer",
      "minimum": 1,
      "description": "Numeric Leadverse lead id."
    },
    "message": {
      "type": "string",
      "minLength": 1,
      "maxLength": 10000,
      "description": "The exact DM text to send. Never send a draft the user has not seen."
    }
  },
  "required": [
    "lead_id",
    "message"
  ],
  "additionalProperties": false
}
POST/v1/dm-queueQueue Reddit DMs · Read and write key · Asynchronous

Queue Reddit DMs to leads. Leadverse delivers them automatically using the account's pacing and daily limits - there is no further confirmation step, so treat queueing as the user's approval to send. Leave content empty and Leadverse writes each DM in the campaign voice just before it goes out. Leads whose Reddit author was already DM'd or queued (even via another lead) are skipped and reported in skippedAuthorContacted. Pass dry_run: true to check capacity and eligibility without queueing anything.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign the leads belong to."
    },
    "lead_ids": {
      "type": "array",
      "items": {
        "type": "integer",
        "minimum": 1,
        "description": "Numeric Leadverse lead id."
      },
      "minItems": 1,
      "maxItems": 50,
      "uniqueItems": true
    },
    "dry_run": {
      "type": "boolean",
      "description": "Report what would happen without queueing."
    }
  },
  "required": [
    "campaign_id",
    "lead_ids"
  ],
  "additionalProperties": false
}
GET/v1/dm-queueList queued DMs · Read scope

List DMs waiting to be sent, with their scheduled send time and current content.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Restrict to one campaign."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}
PATCH/v1/dm-queue/{queued_dm_id}Edit queued DM · Read and write key · Asynchronous

Rewrite the content of a queued DM. Only possible while it is still waiting - once Leadverse starts sending it, the edit is refused.

URL parameters: queued_dm_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "queued_dm_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Queued DM to edit."
    },
    "content": {
      "type": "string",
      "minLength": 1,
      "maxLength": 5000
    }
  },
  "required": [
    "queued_dm_id",
    "content"
  ],
  "additionalProperties": false
}
POST/v1/dm-queue/{queued_dm_id}/generateGenerate queued DM · Read and write key · Asynchronous

Generate or regenerate the content of a queued DM using the same campaign settings and protected workflow as the Generate button in the Leadverse queue. Consumes one AI reply allowance and saves the generated text directly onto the queued DM, where it can still be edited before delivery.

URL parameters: queued_dm_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "queued_dm_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Queued DM whose content should be generated."
    }
  },
  "required": [
    "queued_dm_id"
  ],
  "additionalProperties": false
}
DELETE/v1/dm-queue/{queued_dm_id}Remove queued DM · Read and write key · Asynchronous

Remove a DM from the queue so it is never sent. Only possible while it is still waiting.

URL parameters: queued_dm_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "queued_dm_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Queued DM to remove."
    }
  },
  "required": [
    "queued_dm_id"
  ],
  "additionalProperties": false
}
GET/v1/campaigns/{campaign_id}/outreach-agentGet outreach agent · Read scope

Read the outreach agent configuration for a campaign: whether it is on, its daily DM limit, the minimum lead score it acts on, and follow-up settings.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to read."
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
PATCH/v1/campaigns/{campaign_id}/outreach-agentConfigure outreach agent · Read and write key · Asynchronous

Configure the outreach agent. Enabling it lets Leadverse find qualifying leads and send them DMs automatically, every day, without asking again - say this plainly to the user and get explicit confirmation before enabling. Requires a connected Reddit account, which must be set up in the Leadverse UI. The daily limit is capped by the plan. If the response includes reddit_account_health, relay that warning to the user - their Reddit account is at elevated risk of being flagged for automated DMs.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Campaign to configure."
    },
    "is_enabled": {
      "type": "boolean"
    },
    "daily_limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100
    },
    "min_lead_score": {
      "type": "integer",
      "minimum": 1,
      "maximum": 10
    },
    "followup_enabled": {
      "type": "boolean"
    },
    "followup_reminder_delay_days": {
      "type": "integer",
      "minimum": 1,
      "maximum": 7
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}

Conversations

GET/v1/conversationsList conversations · Read scope

List Reddit DM threads with their normalized latest messages. Uses the same four sort options as the Leadverse Conversations screen and returns pagination metadata.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Restrict to one campaign."
    },
    "status": {
      "type": "string",
      "enum": [
        "all",
        "replied",
        "awaiting"
      ],
      "description": "Matches the All, Replied and Awaiting tabs in the Conversations screen."
    },
    "sort": {
      "type": "string",
      "enum": [
        "priority",
        "recent_activity",
        "newest_reply",
        "newest_sent"
      ],
      "description": "Defaults to priority, matching the Conversations screen."
    },
    "search": {
      "type": "string",
      "maxLength": 200,
      "description": "Search recipient, subreddit, post title, tags or latest message."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}
GET/v1/conversations/{conversation_id}Get conversation · Read scope

Read one page of a Reddit DM thread, newest page first. Messages inside each page are returned oldest to newest; use offset and has_more to load earlier messages.

URL parameters: conversation_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "conversation_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Conversation to read."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "required": [
    "conversation_id"
  ],
  "additionalProperties": false
}
POST/v1/conversations/{conversation_id}/repliesReply to conversation · Read and write key · Asynchronous

Send a reply in an existing Reddit DM thread from the connected account. This is where links and specifics belong - the conversation is already live, so Reddit spam filtering is no longer a concern. Only possible when the lead sent the most recent message - you cannot send two messages in a row without a response, exactly as in the Leadverse inbox. Sends immediately and cannot be undone, so always show the user the exact text and get confirmation first. Subject to the daily reply limit.

URL parameters: conversation_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "conversation_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Conversation to reply in."
    },
    "message": {
      "type": "string",
      "minLength": 1,
      "maxLength": 10000
    }
  },
  "required": [
    "conversation_id",
    "message"
  ],
  "additionalProperties": false
}

Insights

GET/v1/campaigns/{campaign_id}/insights/leadsLead insights · Read scope

Lead analytics: volume over time, top subreddits, buying-intent breakdown and recurring pain points.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Restrict to one campaign."
    },
    "days": {
      "type": "integer",
      "minimum": 1,
      "maximum": 365
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
GET/v1/campaigns/{campaign_id}/insights/outreachOutreach insights · Read scope

Outreach analytics: DMs sent, reply rate and outcomes over time.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Restrict to one campaign."
    },
    "days": {
      "type": "integer",
      "minimum": 1,
      "maximum": 365
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
GET/v1/campaigns/{campaign_id}/market-signalsMarket signals · Read scope

Demand signals mined from tracked discussions - what people are asking for, complaining about and comparing. Pass category "competitors" for competitor-specific chatter.

URL parameters: campaign_id. Omit these from the body.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_id": {
      "type": "string",
      "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
      "description": "Restrict to one campaign."
    },
    "category": {
      "type": "string",
      "enum": [
        "market",
        "competitors"
      ]
    },
    "sort": {
      "type": "string",
      "enum": [
        "top",
        "recent"
      ]
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "required": [
    "campaign_id"
  ],
  "additionalProperties": false
}
GET/v1/competitorsCompetitors · Read scope

Tracked competitors with their recent posts, engagement and trend data.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "campaign_ids": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
        "description": ""
      },
      "maxItems": 20
    },
    "days": {
      "type": "integer",
      "minimum": 1,
      "maximum": 365
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}
GET/v1/viral-postsViral posts · Read scope

High-performing Reddit posts in the account's tracked subreddits, useful as templates for what gets traction.

Parameter schema (including URL parameters)
json
{
  "type": "object",
  "properties": {
    "subreddits": {
      "type": "array",
      "items": {
        "type": "string",
        "maxLength": 100
      },
      "maxItems": 20
    },
    "search": {
      "type": "string",
      "maxLength": 200
    },
    "sort_by": {
      "type": "string",
      "enum": [
        "likes",
        "comments",
        "post_created_date"
      ]
    },
    "sort_order": {
      "type": "string",
      "enum": [
        "asc",
        "desc"
      ]
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 50,
      "description": "Rows to return (max 50)."
    },
    "offset": {
      "type": "integer",
      "minimum": 0,
      "maximum": 100000
    }
  },
  "additionalProperties": false
}